爬虫场景:
- 最基本HTML页面的爬取:只需请求页面url得到页面的HTML源码,通过解析页面得到数据。
- Ajax数据爬取:现在许多网页出于对页面效果的考虑,使用Ajax异步加载数据。这样的网页可以分析页面的Ajax请求,得到请求url和参数模拟进行数据获取。
- 动态渲染页面爬取:有些页面是不能在网页看到真正的HTML,这些HTML可能是通过JavaScript渲染的。爬虫可以通过模拟浏览器登录来进行获取。
- 模拟登录:有些网站必须通过登录才能获取数据,爬虫就必须模拟登录后才有可能得到想要的数据。
- 代理的使用:很多网站都有反爬措施,一个ip大量获取数据可能会被封,需要使用大量代理ip进行隐藏。
APP的爬取:移动端的信息只能在手机上看到,爬虫必须模拟手机的操作或者破解数据请求方式才有可能获取相应数据。
webmagic框架
对比了java爬虫的各大框架,还是webmagic这个框架用的比较舒服,而且源码看过去也不是很难理解,里面的设计模式也足够我们去学习研究,只可惜这个开源项目停更了,地址是:url,当然这里面有很多demo案例,我就参照作者给出的案例来爬取新浪博客,也就是这个开源作者的文章来进行一次实践。不过这个框架存在局限性只能爬取一些指定网页的信息,然后分析其html进行一个抓取。
新浪博客
分析爬取过程
首先我们要爬取的博主文章首页地址为:http://blog.sina.com.cn/s/articlelist_1487828712_0_1.html,此文我们对其中的html分析得到以下几点
列表页:的格式是“http://blog.sina.com.cn/s/articlelist_1487828712_0_1.html“, 其中“0_1”中的“1”是可变的页数。所以我们在对应页面点击下一页也就自然而然变成了0_2了,而总共多少页则可以从第一次访问的页面可以拿到
- 文章页:的格式是“http://blog.sina.com.cn/s/blog_xxxxx.html”, 其中“xxxxx”是可变的字符。我们可以使用正则表达式http://blog\.sina\.com\.cn/s/blog_\w+\.html对URL进行一次粗略过滤。这里比较复杂的是,这个URL过于宽泛,可能会抓取到其他博客的信息,所以我们必须指定对应的div,我们使用xpath//div[@class=\“articleList\“]
参考文章地址:http://webmagic.io/docs/zh/posts/chx-cases/basic-list-target.html
示例代码如下:大概就是首先访问我们的http://blog.sina.com.cn/s/articlelist_1487828712_0_1.html,判断它是不是我们的列表页,如果是第一次访问则抓取我们所需的总共页码。然后将该页面的所有文章引用地址抓下来。如果是文章地址则抓取文章的各项信息填充即可。然后通过自定义一个pipline责任链,因为有的文章是加密了的,所以会出现抓取不到文章的情况我这里是采取的策略是直接过滤。
代码
SinaBlogProcessor
package us.codecraft.webmagic.humm.processor;
import us.codecraft.webmagic.Page;
import us.codecraft.webmagic.Site;
import us.codecraft.webmagic.Spider;
import us.codecraft.webmagic.humm.pipeline.SinaBlogPipeline;
import us.codecraft.webmagic.humm.vo.SinaBlog;
import us.codecraft.webmagic.processor.PageProcessor;
import us.codecraft.webmagic.selector.Selectable;
import java.util.concurrent.atomic.AtomicInteger;
/**
* @author heian
* @data 2021-11-02 11:50:20
*/
public class SinaBlogProcessor implements PageProcessor {
public static final String URL_LIST = "http://blog\\.sina\\.com\\.cn/s/articlelist_1487828712_0_\\d+\\.html";
public static final String URL_ARTICLE = "http://blog\\.sina\\.com\\.cn/s/blog_\\w+\\.html";
AtomicInteger articleNum = new AtomicInteger(0);
AtomicInteger pagesNum = new AtomicInteger(0);
Integer pages = 1;
private Site site = Site
.me()
.setDomain("blog.sina.com.cn")
.setSleepTime(200);
@Override
public void process(Page page) {
if (pagesNum.get() == 0){
// 文章的pages(页数)
pages = Integer.parseInt(page.getHtml().xpath("//ul[@class='SG_pages']//span/text()").toString().replace("共","").replace("页",""));
System.out.println("共有" + pages + "页码");
}
//pages = pages;
if (page.getUrl().regex(URL_LIST).match()) {
System.out.println("开始访问第-------------------" + pagesNum.addAndGet(1) + "页数---------------------");
page.addTargetRequests(page.getHtml().xpath("//div[@class=\"articleList\"]").links().regex(URL_ARTICLE).all());//文章所有地址
if (pagesNum.get() < pages){
page.addTargetRequest("http://blog.sina.com.cn/s/articlelist_1487828712_0_" + (pagesNum.get()+1) + ".html");
}
//page.addTargetRequests(page.getHtml().links().regex(URL_LIST).all().stream().distinct().collect(Collectors.toList()));//列表页所有地址 这个不正确,会包含很对无效地址
} else {
articleNum.addAndGet(1);
System.out.println("文章排序数--------------------:" + articleNum.get() + "--------------------");
// <h2 id="t_58ae76e80100to5q" class="titName SG_txta">zzSed学习笔记</h2>
Selectable titleSelectable = page.getHtml().xpath("//div[@class='articalTitle']/h2/text()");
// <span class="time SG_txtc">(2011-10-08 21:08:07)</span>
Selectable dataSelectable = page.getHtml().xpath("//div[@id='articlebody']//span[@class='time SG_txtc']").regex("\\((.*)\\)");
Selectable contentSelectable = page.getHtml().xpath("//div[@id='articlebody']//div[@class='articalContent']");
SinaBlog entity = new SinaBlog();
entity.setTitle(titleSelectable.toString());
entity.setDate(dataSelectable.toString());
entity.setContent(contentSelectable.toString());
if (entity.getTitle() == null){
System.out.println("第:" + articleNum.get() + "文章数据搜索不到");
page.setSkip(true);
}else {
page.putField("sinaBlog",entity);
}
}
}
@Override
public Site getSite() {
return site;
}
public static void main(String[] args) {
Spider.create(new SinaBlogProcessor()).addUrl("http://blog.sina.com.cn/s/articlelist_1487828712_0_1.html")
.addPipeline(new SinaBlogPipeline())
.run();
}
}
SinaBlogPipeline
package us.codecraft.webmagic.humm.pipeline;
import us.codecraft.webmagic.ResultItems;
import us.codecraft.webmagic.Task;
import us.codecraft.webmagic.humm.vo.SinaBlog;
import us.codecraft.webmagic.pipeline.Pipeline;
import java.util.ArrayList;
import java.util.List;
public class SinaBlogPipeline implements Pipeline {
private List<SinaBlog> result = new ArrayList<>();
@Override
public void process(ResultItems resultItems, Task task) {
Object blog = resultItems.get("sinaBlog");
if (blog != null){
SinaBlog b = (SinaBlog) blog;
result.add(b);
System.out.println("爬取文章排列数:" + result.size() + ",文章标题:" + b.getTitle());
}
}
}
SinaBlog
package us.codecraft.webmagic.humm.vo;
/**
* @author heian
* @date 2021/11/2 11:18
*/
public class SinaBlog {
private String title;
private String date;
private String content;
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getDate() {
return date;
}
public void setDate(String date) {
this.date = date;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
}
控制台输出信息
共有2页码
开始访问第-------------------1页数---------------------
文章排序数--------------------:1--------------------
爬取文章排列数:1,文章标题:zzSed学习笔记
文章排序数--------------------:2--------------------
爬取文章排列数:2,文章标题:通往架构师之路(一) WEB环境的原理、构建和调试
文章排序数--------------------:3--------------------
爬取文章排列数:3,文章标题:怀旧帖——祖格还是那个祖格,而我们已不是当初的我们
文章排序数--------------------:4--------------------
爬取文章排列数:4,文章标题:编程为什么有趣? 太有共鸣了
文章排序数--------------------:5--------------------
第:5文章数据搜索不到
文章排序数--------------------:6--------------------
爬取文章排列数:5,文章标题:瓦尔登湖 节选1
文章排序数--------------------:7--------------------
爬取文章排列数:6,文章标题:zzAndroid进程管理机制及优化 for HTC HeRO(其它设备也可参考)
文章排序数--------------------:8--------------------
爬取文章排列数:7,文章标题:句法树图形化程序心得
文章排序数--------------------:9--------------------
爬取文章排列数:8,文章标题:23个经典JDK设计模式
文章排序数--------------------:10--------------------
爬取文章排列数:9,文章标题:一些IR相关概念
文章排序数--------------------:11--------------------
爬取文章排列数:10,文章标题:Java Core学习手记--Class loader和Class对象
文章排序数--------------------:12--------------------
第:12文章数据搜索不到
文章排序数--------------------:13--------------------
爬取文章排列数:11,文章标题:做的最失败的事和最成功的事总结--2010年9-10月
文章排序数--------------------:14--------------------
爬取文章排列数:12,文章标题:Java源码研究日记--HashMap与HashTable
文章排序数--------------------:15--------------------
爬取文章排列数:13,文章标题:图解JVM在内存中申请对象及垃圾回收流程-转载
文章排序数--------------------:16--------------------
爬取文章排列数:14,文章标题:学习awk
文章排序数--------------------:17--------------------
爬取文章排列数:15,文章标题:设计模式小结
文章排序数--------------------:18--------------------
爬取文章排列数:16,文章标题:NOSQL
文章排序数--------------------:19--------------------
爬取文章排列数:17,文章标题:Java实现贪食蛇的程序
文章排序数--------------------:20--------------------
爬取文章排列数:18,文章标题:发现一个很好的blog
文章排序数--------------------:21--------------------
爬取文章排列数:19,文章标题:Java一些小知识(持续更新)
文章排序数--------------------:22--------------------
爬取文章排列数:20,文章标题:静态与动态
文章排序数--------------------:23--------------------
第:23文章数据搜索不到
文章排序数--------------------:24--------------------
爬取文章排列数:21,文章标题:Java怎么解决接口名冲突?
文章排序数--------------------:25--------------------
爬取文章排列数:22,文章标题:我的博客今天1岁204天啦!
文章排序数--------------------:26--------------------
爬取文章排列数:23,文章标题:Java中的内存泄露
文章排序数--------------------:27--------------------
爬取文章排列数:24,文章标题:第一次面试总结
文章排序数--------------------:28--------------------
爬取文章排列数:25,文章标题:[原创]一句话解释数据结构
文章排序数--------------------:29--------------------
爬取文章排列数:26,文章标题:读懂异步请求
文章排序数--------------------:30--------------------
爬取文章排列数:27,文章标题:(转载)JAVA 内部类的使用
文章排序数--------------------:31--------------------
爬取文章排列数:28,文章标题:面试笔记-一些基本概念
文章排序数--------------------:32--------------------
爬取文章排列数:29,文章标题:基础中的基础-几种排序算法及分析
文章排序数--------------------:33--------------------
爬取文章排列数:30,文章标题:设计模式之观察者模式--理解设计模式的用处
文章排序数--------------------:34--------------------
爬取文章排列数:31,文章标题:设计模式日志-策略模式
文章排序数--------------------:35--------------------
爬取文章排列数:32,文章标题:终于明白,扯淡的UML
文章排序数--------------------:36--------------------
爬取文章排列数:33,文章标题:李践--砍掉成本
文章排序数--------------------:37--------------------
爬取文章排列数:34,文章标题:gmail联系人获取方式
文章排序数--------------------:38--------------------
爬取文章排列数:35,文章标题:PHP:pear和heredoc
文章排序数--------------------:39--------------------
爬取文章排列数:36,文章标题:Ultraedit正则表达式使用技巧
文章排序数--------------------:40--------------------
爬取文章排列数:37,文章标题:使用SVN的教训一则
文章排序数--------------------:41--------------------
爬取文章排列数:38,文章标题:从“移动商街”说起
文章排序数--------------------:42--------------------
爬取文章排列数:39,文章标题:Wordpress的数据库结构
文章排序数--------------------:43--------------------
爬取文章排列数:40,文章标题:赢在南京?
文章排序数--------------------:44--------------------
爬取文章排列数:41,文章标题:动态规划算法的变种--备忘录算法
文章排序数--------------------:45--------------------
第:45文章数据搜索不到
文章排序数--------------------:46--------------------
爬取文章排列数:42,文章标题:分治算法的一点思考--为什么大多使用二分法?
文章排序数--------------------:47--------------------
爬取文章排列数:43,文章标题:初学PS 贴2张做的海报
文章排序数--------------------:48--------------------
爬取文章排列数:44,文章标题:一道来自百度的面试题--倒排索引的AND操作
文章排序数--------------------:49--------------------
爬取文章排列数:45,文章标题:[转载][MS大牛蛙]一些面试就业Tips送给在校生
开始访问第-------------------2页数---------------------
Disconnected from the target VM, address: '127.0.0.1:59116', transport: 'socket'
Process finished with exit code 0
pin图抓取
分析爬取过程
我想按照我搜索的关键字去把查询到的图片收集起来,然后存在数据库或者其它地方。但是这个网站必须先登录,而且是个外网网站所以也需要翻墙。所以需要设置一下代理网站还有将自身登录该网站的的cookie传入进去发请求即可。
比如我输入“时尚” 关键字去进行一个搜索:https://www.pinterest.com/search/pins/?q=%E6%97%B6%E5%B0%9A&rs=filter 它对url进行了一个编码 %E6%97%B6%E5%B0%9A 这一部分就是“时尚”二字。但是搜出来的东西经过定位只有那么20几条,无法进行一个分页查询,在浏览器network中也没法看到它的查询的信息,所以此处我只能爬到这么多,很遗憾。还是太菜了。但是经过这几天的学习,发现python对爬虫的支持还是很完美的,所以后面我用python实现了此功能。具体可参见我这篇文章:文章地址
代码
package us.codecraft.webmagic.humm.processor;
import com.alibaba.fastjson.JSON;
import us.codecraft.webmagic.Page;
import us.codecraft.webmagic.Request;
import us.codecraft.webmagic.Site;
import us.codecraft.webmagic.Spider;
import us.codecraft.webmagic.downloader.HttpClientDownloader;
import us.codecraft.webmagic.processor.PageProcessor;
import us.codecraft.webmagic.proxy.Proxy;
import us.codecraft.webmagic.proxy.SimpleProxyProvider;
import java.util.List;
import java.util.stream.Collectors;
/**
* @author heian
* @data 2021-11-02 11:50:20
*/
public class PhotoProcessor implements PageProcessor {
private static Site site = Site
.me()
.setSleepTime(200);
@Override
public void process(Page page) {
List<String> list = page.getHtml().xpath("//div[@class='vbI XiG']//div[@data-test-id='pin']//img").$("img", "srcset").all();
List<String> collect = list.stream().map(single -> {
String[] split = single.split(",");
String imgUrl = split[3];
return imgUrl.substring(1, imgUrl.length() - 3);
}).collect(Collectors.toList());
System.out.println(JSON.toJSONString(collect));
}
@Override
public Site getSite() {
return site;
}
public static void main(String[] args) {
String cook = "我的cookie";
HttpClientDownloader httpClientDownloader = new HttpClientDownloader();
httpClientDownloader.setProxyProvider(SimpleProxyProvider.from(new Proxy("192.168.1.148",7890)));
Request request = new Request();
request.addCookie("cookie",cook);
request.setUrl("https://www.pinterest.com/search/pins/?q=%E6%97%B6%E5%B0%9A&rs=filter");
Spider.create(new PhotoProcessor())
.setDownloader(httpClientDownloader)
.addRequest(request)
//.addUrl("https://www.pinterest.com/")
.run();
}
}
控制台
["https://i.pinimg.com/originals/0c/0a/e0/0c0ae06bde006a272b41345b953bb767.png","https://i.pinimg.com/originals/29/8b/22/298b2272949019abeaf876461a9e53a9.jpg","https://i.pinimg.com/originals/e1/dc/76/e1dc76618bc6e7e0d58d1a30b100bef6.jpg","https://i.pinimg.com/originals/40/fc/9d/40fc9d348fccddfcc0eaba6708afcfc9.jpg","https://i.pinimg.com/originals/84/dd/2a/84dd2ab19554c11f5f4cf909ac90357c.jpg","https://i.pinimg.com/originals/23/40/3d/23403d1d2ef92541b004ff75bf81b1bb.jpg","https://i.pinimg.com/originals/65/03/bc/6503bcdf0325d6c2c44fb35610fd38bd.jpg","https://i.pinimg.com/originals/b1/a6/ab/b1a6abe7c79acedb25395ed5e9d51aca.jpg","https://i.pinimg.com/originals/2b/62/ab/2b62ab8d5e7b98ab6682821be0a90f54.jpg","https://i.pinimg.com/originals/d0/fd/85/d0fd858532458b55b395bfe90271fb6c.png","https://i.pinimg.com/originals/b5/c0/b6/b5c0b6f894f1169a5bfa975008dc13d4.jpg","https://i.pinimg.com/originals/f7/af/dc/f7afdcc79c14ba018730b10341bf590e.jpg","https://i.pinimg.com/originals/1b/6d/38/1b6d384e9c74184298ca837e6b885ae6.jpg","https://i.pinimg.com/originals/a7/15/81/a7158102899943b6400390979fa9347a.png","https://i.pinimg.com/originals/0a/55/85/0a558586f025a650dd1be2d1799c483f.jpg","https://i.pinimg.com/originals/32/b8/11/32b81154b01016a46e0016771f64ae5e.jpg","https://i.pinimg.com/originals/5f/fe/49/5ffe498f43fca63d579044580ab2dccf.jpg","https://i.pinimg.com/originals/6e/40/ea/6e40ea6af9921f1e201425848cc5ce02.jpg","https://i.pinimg.com/originals/6e/3a/d3/6e3ad37ccdc40eefbb7e7194ad6e7044.jpg","https://i.pinimg.com/originals/81/54/2c/81542c93b177ee0e923918cea5b12dff.jpg","https://i.pinimg.com/originals/92/05/6c/92056cefc6f200581e509d1e69e1ab8f.jpg","https://i.pinimg.com/originals/36/b3/c5/36b3c5f38971fbcac8b57db24086e161.png","https://i.pinimg.com/originals/ba/53/4e/ba534ed0796adda887d57a0ef12f7d98.jpg"]
get page: https://www.pinterest.com/search/pins/?q=%E6%97%B6%E5%B0%9A&rs=filter
Disconnected from the target VM, address: '127.0.0.1:63497', transport: 'socket'
Process finished with exit code 0