之前在论坛看到一位大佬发了一个爬妹子图的爬虫程序,地址https://www.52pojie.cn/forum.php?mod=viewthread&tid=1227603&extra=page%3D1%26filter%3Dtypeid%26typeid%3D192
受这篇帖子的启发,利用其思路,自己写了一个爬去妹子图的java方法体,可以爬取https://www.mzitu.com的妹子图,哈哈,仅此分享给大家。另外,有一位大佬直接分享了成品,我这里的呢,就是大家动动脑子,自己爬取了。大佬作品地址https://www.52pojie.cn/forum.php?mod=viewthread&tid=828175&extra=page%3D1%26filter%3Dtypeid%26typeid%3D192
那么先来说说思路吧。(思路得益于低一个贴出代码的帖子)第一步:先利用jsoup获取主页上图片的链接地址;第二步:进入主页中地址的二级地址,判断是否是图片地址,如果是,利用保存程序保存图片,如果不是,进入下一级地址,如此往复,直到爬取完图片。
那么上代码了:
[Asm] 纯文本查看 复制代码 public static void getTestContent() {
ArrayList<String> filePath = new ArrayList<>();//构建文件路径集合
ArrayList<String> fileId = new ArrayList<>();//构建独一无二的文件名
String url ="https://www.mzitu.com/";
try {
//解析第一级地址中返回的链接地址,可以利用System.out.println(doc.toString()); 查看网页源代码,确定如何利用jsoup获取下一级地址
Document doc = Jsoup.connect(url).get();
Elements el = doc.getElementsByClass("postlist");
Elements li = el.select("ul").select("li").select("a");
int num=0;
for (Element element : li){//遍历获取的地址
num=num+1;//统计第一级地址的数量
String url1 = String.valueOf(element.attr("href"));//获取第二层的href
if ((num%2)==0){//有些href是广告 和图片链接最主要的区别是 含有字母p 所以判断一下
continue;
}
String ID = url1.substring(22,28);//获取文件ID ,这个值用来保存图片,但这里只是利用一级地址生成文件前缀
String url2=url+ID;
//下面内容为获取二级地址的网络请求
Document document = Jsoup.connect(url2).get();
Elements ending = document.getElementsByClass("pagenavi");
Elements img = ending.select("a");
int tmpnum=0;
int lastnum=0;
for(Element element2:img) {
tmpnum=tmpnum+1;//通过导航栏目统计二级网页中同一个系列的图片数目
}
//下面就是获取最终同一个系列有多少图片
if(tmpnum>2) {
lastnum = Integer.valueOf(StringTools.getLastStringBySeperate(img.get(tmpnum-2).attr("href").toString(),"/"));//根据最后一张图的序号判断一个系列有多少图片
}else {
if(tmpnum==0) {lastnum=0;}
if(tmpnum==1) {lastnum=3;}
}
//下面是获取最终每张图片的下载地址
for(int i=2;i<lastnum;i++) {
String url3=url2+"/"+String.valueOf(i);//生成同一个系列每张图的地址
Document docss = Jsoup.connect(url3).get();
Elements elss = docss.getElementsByClass("main-image");
Elements liss = elss.select("p").select("a").select("img");
String url4 = String.valueOf(liss.get(0).attr("src"));
filePath.add(url4);//保存妹子图的地址
System.out.println(url4);
try {
Thread.sleep(500);//延迟请求,服务器似乎存在防止拒绝服务攻击的策略,快速大量请求会被服务器拒绝
} catch (InterruptedException e) {
e.printStackTrace();
}
if(i<10) {
fileId.add(ID+"0"+String.valueOf(i));//保证文件名统一长度
}else {
fileId.add(ID+String.valueOf(i));//生成唯一的文件名,需要之前的文件前缀加序号
}
}
}
SavePngJoup(filePath, fileId);//利用地址和文件名保存图片
} catch (IOException e) {
System.out.println("抓取文件过程中出错,可能是请求过于频繁服务器拒绝响应");
e.printStackTrace();
}finally {
System.out.println("运行结束");
}
}
[Asm] 纯文本查看 复制代码 //使用这个方法前请自己在D盘建立test文件夹,否则会报错
public static void SavePngJoup(ArrayList<String> filePath,ArrayList<String> fileId) {
ByteArrayOutputStream byteOutputStream = null;
FileOutputStream fileOutputStream = null;
DataInputStream dataInputStream = null;
//遍历保存图片
for (int index = 0 ; index < filePath.size() ; index++){
String url = null;
try {
url = new String(filePath.get(index));//读取地址
//下面的网络请求设置服务端,否则,请求次数多了服务器拒绝响应
Connection connection = Jsoup.connect(url).userAgent("Mozilla/5.0 (Windows; U; WindowsNT 5.1; en-US; rv1.8.1.6) Gecko/20070725 Firefox/2.0.0.6").referrer("https://www.mzitu.com/");
Connection.Response response = connection.method(Connection.Method.GET).ignoreContentType(true).timeout(3*1000).execute();
BufferedInputStream bufferedInputStream = response.bodyStream();
try {
Thread.sleep(500);//模拟人操作,人操作总会耗时,具体延迟多少看个人自己测试了。
//另外,这里等待时间可以是500左右的随机数,这样就更真实模拟人的点击
} catch (InterruptedException e) {
e.printStackTrace();
}
dataInputStream = new DataInputStream(bufferedInputStream);
File file = new File("D:/test/"+fileId.get(index)+".jpg");
fileOutputStream = new FileOutputStream(file);
byteOutputStream = new ByteArrayOutputStream();
byte[] buffer = new byte[1024];
int length;
/*写入字节*/
while ((length = dataInputStream.read(buffer))>0){
byteOutputStream.write(buffer,0,length);
}
byte[] context = byteOutputStream.toByteArray();
fileOutputStream.write(context);
System.out.println("保存了"+file.getName());
} catch (MalformedURLException e) {
System.out.println("URL转换错误");
e.printStackTrace();
} catch (FileNotFoundException e) {
System.out.println("无法创建文件");
e.printStackTrace();
} catch (IOException e) {
System.out.println("文件传输出现错误");
e.printStackTrace();
}
}
/*关闭流*/
try {
fileOutputStream.close();
byteOutputStream.close();
dataInputStream.close();
} catch (IOException e) {
System.out.println("关闭流出现错误");
e.printStackTrace();
}
}
最后贴上自己写的一个工具类
[Asm] 纯文本查看 复制代码 public class StringTools {
//parameter : str
//the string you need to process
//parameter : fenggefu
//the seperate code in string, such as the "," in the string"Type=neme, Date=20200725"
//for example: str="haha=haha,didi=didi"
//res=StringTools.seperateString(str,",") will return an array res[0]="haha=haha",res[1]="didi=didi"
public static String[] seperateString(String str,String fenggefu) {
char fengefu=fenggefu.charAt(0);
int len=str.length();
char ss[]=str.toCharArray();
int numberofCode=1;
for(int i=0;i<len;i++) {
if(ss[i]==fengefu) {numberofCode=numberofCode+1;}
}
String properties[];
if(numberofCode>0) {
int tmpnum=0;
properties = new String[numberofCode];
for(int i=0;i<numberofCode;i++) {
properties[i]="";
}
for(int i=0;i<len;i++) {
if(ss[i]!=fengefu) {
properties[tmpnum]=properties[tmpnum]+ss[i];
}
if(ss[i]==fengefu) {
tmpnum=tmpnum+1;
}
}
}else {
properties = new String[1];
properties[0]=str;
}
return properties;
}
public static String getLastStringBySeperate(String str,String fenggefu) {
String[] reStrings=seperateString(str, fenggefu);
return reStrings[reStrings.length-1];
}
}
这个工具类可以把字符串分为一个数组。在本程序中之用为了获取"/"分隔的最后一组数字。有以上代码,只要在主方法调用就可以爬图了。图片很多,爬取需要时间,请耐心等待。
|