4. 爬取Maven中央仓库的爬虫程序实现
一开始计划获取maven中央仓库中所有组件文件,原以为使用wget命令或网络镜像工具就可以从https://repo.maven.apache.org/maven2/网站轻松获取。可惜理想很丰满,现实很有骨感,既然获取不到,那就自己实现一个爬虫获取。
提示:本文仅仅用于技术研究的目的,并不用于其他目的,更不得使用该技术对Maven中央库带来任何不利的影响。
4.1. 分析过程
4.1.1. 打开页面
打开仓库页面:https://repo.maven.apache.org/maven2/
页面上都是以目录和文件的方式展示的。
4.1.2. 查看页面源码
可以轻易的发现目录和文件的内容都是在id为“contents”下的a标签中。
4.1.3. 版本信息查看(在maven-metadata.xml)
不断深入某个目录,可以轻易的发现组件的版本信息都在maven-metadata.xml中进行描述。例如:
https://repo.maven.apache.org/maven2/tech/ibit/sql-builder/maven-metadata.xml 的内容。
<?xml version="1.0" encoding="UTF-8"?>
<metadata>
<groupId>tech.ibit</groupId>
<artifactId**>sql-builder</artifactId>
<versioning>
<latest>2.0</latest>
<release>2.0</release>
<versions>
<**version**>1.0</version>
<version>1.1</version>
<version>2.0</version>
</versions>
<lastUpdated>20201130115230</lastUpdated>
</versioning>
</metadata>
可见maven-metadata.xml中包含groupId,artifactId和version信息。
4.1.4. 使用Jsoup获得下载链接
private static void findSubUrl(String url, int sleepMillis) {
try {
Thread.sleep(sleepMillis);//设置睡眠时间(避免ip被封)
Document doc=null;
boolean needreconnect=true;
while(needreconnect) {
try {
doc = Jsoup.connect(url).userAgent("Mozilla").timeout(5000).get();
}
catch (SocketTimeoutException te)
{
//链接超时,等待重连,10秒
Thread.sleep(10 * 1000);
//System.out.println("链接超时,等待重连,10秒");
logger.warn("链接超时,等待10秒重连");
needreconnect=true;
continue;
}
needreconnect=false;
}
Elements links = doc.select("#contents a");
for (Element link : links) {
String pathorfilename = link.attr("href");
if (pathorfilename.equals("../")) {
//上级目录,不处理
continue;
}
//创建文件夹
//获得绝对URL
String absUrl = link.absUrl("href");
System.out.println(absUrl);
logger.info("{}",absUrl);
..................
}
4.2. 获取顶层目录文件
4.2.1. 设置基本配置参数
/**
* 爬取根目录
*/
//private static final String ROOT = "https://repo.maven.apache.org/maven2/";
private static final String ROOT = "https://repo1.maven.org/maven2/";
/**
* 硬盘存取根目录
*/
private static final String DiskROOT = "E:\\maven2\\";
/**
* 全部顶层索引文件
*/
private static String indexfilename="maven2Indexall.txt";
/**
* maven-metadata.xml文件名
*/
private static final String MAVEN_METADATA_XML_FILENAME = "maven-metadata.xml";
private static final Logger logger = LoggerFactory.getLogger(Laucher.class);
建立文件夹函数
private static void searchdir(String rooturl,String dir,int sleepMillis)
{
String filePath = DiskROOT + dir;
File f = new File(filePath);
if (!f.exists()){
boolean flag2 = f.mkdir();
if (!flag2){
logger.error("文件夹创建失败:{}", filePath);
}
}
String suburl = rooturl + dir;
findSubUrl(suburl, sleepMillis);
}
4.2.2. 对Maven仓库顶层目录进行分字母爬取
public static void main(String[] args) {
String firstAlpaca="all"; //all 全部爬取,失败概率大,建议分字母 a,b,c...爬取
if (args.length > 0) {
firstAlpaca = args[0];
}
logger.info("Beging crawler:beging with {}",firstAlpaca);
int sleepMillis = 100;
String rooturl = ROOT;
// findSubUrl(rooturl,sleepMillis); //直接爬取全部
File file = new File(DiskROOT+indexfilename);
try {
BufferedReader br= new BufferedReader(new FileReader(file));
String st;
while ((st = br.readLine()) != null) {
System.out.println(st);
String dir=st.trim();
if(firstAlpaca.equals("all")||firstAlpaca.equals("ALL"))
{
searchdir(rooturl, dir,sleepMillis);
}
else
{
int index = dir.toLowerCase().indexOf(firstAlpaca);
if (index == 0) {
//首字母合格
searchdir(rooturl, dir,sleepMillis);
}
}
}
}
catch (FileNotFoundException e)
{
logger.error("找不到文件:{}",indexfilename);
}
catch (IOException ie)
{
logger.error("使用文件失败:{}",indexfilename);
}
logger.info("End crawler");
}
4.3. 递归文件目录处理与文件保存
/**
* 查询子url
* @param url 当前url
* @param sleepMillis 睡眠毫秒数
*/
private static void findSubUrl(String url, int sleepMillis) {
try {
Thread.sleep(sleepMillis);
Document doc=null;
boolean needreconnect=true;
while(needreconnect){
try {
doc = Jsoup.connect(url).userAgent("Mozilla").timeout(5000).get();
} catch (SocketTimeoutException te)
{
//链接超时,等待重连,10秒
Thread.sleep(10 * 1000);
//System.out.println("链接超时,等待重连,10秒");
logger.warn("链接超时,等待10秒重连");
needreconnect=true;
continue;
}
needreconnect=false;
}
Elements links = doc.select("#contents a");
for (Element link : links) {
String pathorfilename = link.attr("href");
if (pathorfilename.equals("../")) {
//上级目录,不处理
continue;
}
//创建文件夹
//获得绝对URL
String absUrl = link.absUrl("href");
System.out.println(absUrl);
logger.info("{}",absUrl);
//获得保存文件路径
int urllen = ROOT.length();
String pathName = absUrl.substring(urllen);
java.util.Date day=new Date();
SimpleDateFormat sdf= new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String nowtime=sdf.format(day);
System.out.println("["+nowtime+"]: "+pathName);
logger.info("[{}]: {}",nowtime,pathName);
//判断是目录还是文件
int ret = pathorfilename.indexOf("/");
if (ret == -1) {
String saveFile = DiskROOT + pathName;
File f1 = null;
//是文件,不是目录
//储存网络文件到硬盘
while(true) {
try {
f1 = new File(saveFile);
if (!f1.exists()) {
//文件不存在才下载
URL httpurl = new URL(absUrl);
BufferedInputStream bis = new BufferedInputStream(httpurl.openStream());
FileOutputStream fis = new FileOutputStream(saveFile);
byte[] buffer = new byte[1024];
int count = 0;
while ((count = bis.read(buffer, 0, 1024)) != -1) {
fis.write(buffer, 0, count);
}
fis.close();
bis.close();
break;
}
} catch (IOException e) {
logger.error("下载文件失败:{}", saveFile);
if (f1.exists()) {
f1.delete();
}
Thread.sleep(10 * 1000);
//System.out.println("链接超时,等待重连,10秒");
logger.warn("文件下载失败,等待10秒重新下载");
//重新下载
continue;
}
}
} else {
//目录
//创建硬盘目录
String filePath = DiskROOT + pathName;
File f2 = new File(filePath);
if (!f2.exists()) {
boolean flag2 = f2.mkdir();
if(!flag2) {
//System.out.println( "文件夹创建失败:"+filePath);
logger.error("创建文件失败:{}",filePath);
}
}
//递归处理
findSubUrl(absUrl, sleepMillis);
}
}
} catch (IOException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
4.4. 生成jar文件,便于使用命令行运行程序
设置Artifacts
注意:注意MANNIFEST.MF目录一定要设置对才行,IDEA默认的有问题。
构建
结果:
在目录D:/SVNJava/Crawler/out/artifacts/Crawler_jar/下生成Crawler.jar文件。
4.5. 测试
命令使用方式:
java -jar Crawler.jar [a-z]
java -jar Crawler.jar a
配套设置:
文件下载目录
E:\maven2
下载索引文件:(https://repo1.maven.apache.org/maven2/首页所有文件目录列表)
E:\maven2\maven2Indexall.txt 测试文件内容如下:
错误日志文件目录如下:
E:\log
测试结果如下:
测试详细结果如下:
可见文件爬取成功。