1. WebMagic 介绍
WebMagic项目代码分为核心和扩展两部分。核心部分(webmagic-core)是一个精简的、模块化的爬虫实现,而扩展部分则包括一些便利的、实用性的功能。WebMagic的架构设计参照了Scrapy,目标是尽量的模块化,并体现爬虫的功能特点。
这部分提供非常简单、灵活的API,在基本不改变开发模式的情况下,编写一个爬虫。
扩展部分(webmagic-extension)提供一些便捷的功能,例如注解模式编写爬虫等。同时内置了一些常用的组件,便于爬虫开发。
另外WebMagic还包括一些外围扩展和一个正在开发的产品化项目webmagic-avalon。底层依然是HttpClient和jsoup.
WebMagic 核心组件介绍
- downloader 下载组件
- PageProcessor 页面解析组件(必须自定义)
- scheculer 访问队列组件
-
2. 入门
添加对应jar文件
```xmlus.codecraft webmagic-core 0.7.5
2. 实现步骤
1. 创建PageProcess 接口实现类java
/
页面分析
@param page 下载结果封装成Page对象,可以从page对象中获取下载结果
*/
@Override
public void process(Page page) {
Html html = page.getHtml();
String htmlStr = html.toString();
// 把结果输出到控制台
// ResultItems resultItems = page.getResultItems();
// resultItems.put(“html”, htmlStr);
// 简化写法
page.putField(“html”, htmlStr);
}
/
返回site对象,site就是站点的配置,返回默认配置也可以,
/
@Override
public Site getSite() {
return Site.me();
}
2. 在实现类中实现页面分析的业务逻辑
2. 初始爬虫
2. 启动java
public static void main(String[] args) {
Spider spider = Spider.create(new TestWebMagic());
// 设置起始URl
spider.addUrl(“https://list.jd.com/list.html?cat=9987%2C653%2C655&s=117&click=0&page=1“);
// 启动爬虫
spider.run();
// 新线程支持爬虫
// spider.start();
}
完整示例代码java
import us.codecraft.webmagic.Page;
import us.codecraft.webmagic.Site;
import us.codecraft.webmagic.Spider;
import us.codecraft.webmagic.processor.PageProcessor;
import us.codecraft.webmagic.selector.Html;
public class TestWebMagic implements PageProcessor {
public static void main(String[] args) {
Spider spider = Spider.create(new TestWebMagic());
// 设置起始URl
spider.addUrl(“https://list.jd.com/list.html?cat=9987%2C653%2C655&s=117&click=0&page=1“);
// 启动爬虫
spider.run();
// 新线程支持爬虫
// spider.start();
}
/
页面分析
@param page 下载结果封装成Page对象,可以从page对象中获取下载结果
*/
@Override
public void process(Page page) {
Html html = page.getHtml();
String htmlStr = html.toString();
// 把结果输出到控制台
// ResultItems resultItems = page.getResultItems();
// resultItems.put(“html”, htmlStr);
// 简化写法
page.putField(“html”, htmlStr);
}
/
返回site对象,site就是站点的配置,返回默认配置也可以,
/
@Override
public Site getSite() {
return Site.me();
}
}
<a name="93c9d5fd"></a>
# 3. 组件介绍
<a name="rRCDW"></a>
## 1. Downloader
下载器组件, 使用HttpClient实现, 如果没有特殊不需要自定义, 默认的组件就可以满足全部需求. 自定义时需要实现Downloader接口向PageProcess 传递数据时, 把结果封装成Page对象
<a name="lTrlf"></a>
## 2. PageProcess
页面分析业的业务组件, 页面分析的逻辑在其中的实现. 需要实现PageProcessor接口
<a name="rJd2d"></a>
### 2.1 Site 介绍
Site 代表一个站点信息, 可以设置抓取的频率, 重试的次数, 超时的时间.
| 方法 | 说明 | 示例 |
| --- | --- | --- |
| setCharset(String) | 设置编码 | site.setCharset("utf-8") |
| setUserAgent(String) | 设置UserAgent | site.setAgent("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/93.0.4577.63 Safari/537.36") |
| setTimeOut(int) | 超时时间,单位毫秒 | site.setTimeOut(5000) |
| setRetryTimes(int) | 设置重复次数 | sietsetRetryTimes(3) |
| setCycleRetryTimes(int) | 设置循环重试次数 | site.setCycleRetryTimes(3) |
| addCoolie(String,String) | 添加Cookie | site.addCookie("token","asdf48451515iu4dfawe") |
| setDomain(String) | 设置域名,需设置域名后,addCookie才生效 | site.setDomain("ww.github.com"); |
| addHeader(String,String) | 添加一条addHeader | site.addHeader("Referer","https://github.com") |
> 如果没有特殊续期, 直接使用默认的配置即可
<a name="mW41b"></a>
### 2.2 Page 介绍
page.getHtml() 返回抓取的结果<br />getResultItems() 返回ResultItems对象, 向pipeline传递数据时使用<br />addTargetRequests(), addTargetRequest() 向scheduler对象中添加URL
<a name="UCc2w"></a>
### 2.3 HTML
html也是一个Selectable对象, 一个Selectable就可以表示dom节点<br />使用HTML解析页面三种方式:
1. 使用原生的jsoup方法进行解析
Document document = html.getDocument();
2. 使用css选择器解析
html.css("选择器")<br />html.$("选择器")
3. 使用xpath语法解析java
public class TestWebMagic1 implements PageProcessor {
public static void main(String[] args) {
Spider.create(new TestWebMagic1()).addUrl(“https://list.jd.com/list.html").start();
}
@Override
public void process(Page page) {
// 使用原生的jsoup解析页面
Html html = page.getHtml();
// 得到一个jsoup的document对象
Document document = html.getDocument();
String title = document.getElementsByTag(“title”).text();
page.putField(“title”, title);
}
@Override
public Site getSite() {
return Site.me();
}
}
4. ResultItems
作用就是把解析的结果传递给pipeline. 可以使用page对象的getResultItems() 方法获得此对象. 也可以直接使用putField()方法将数据添加到ResultItems对象中
5. Request
并不是http请求的Request对象,就是把url封装成Request对象. 可以添加一个, 也可以添加多个
<a name="DH2CF"></a>
## 3. pipeline
数据持久化组件: 提供三个实现
1. ConsolePipeline 向控制台输出, 默认使用
1. FilePipeline 向磁盘文件中输出
1. JsonFilePipeline 保存JSON格式的文件到磁盘中
1. 自定义pipeline
1. 需要实现Pipeline接口java
import java.util.List;
import us.codecraft.webmagic.Page;
import us.codecraft.webmagic.Site;
import us.codecraft.webmagic.Spider;
import us.codecraft.webmagic.pipeline.ConsolePipeline;
import us.codecraft.webmagic.pipeline.FilePipeline;
import us.codecraft.webmagic.processor.PageProcessor;
import us.codecraft.webmagic.selector.Html;
import us.codecraft.webmagic.selector.Selectable;
public class TestWebMagic3 implements PageProcessor {
public static void main(String[] args) {
// 使用spider初始化爬虫
Spider spider = Spider.create(new TestWebMagic3());
// 设置起始URL
spider.addUrl(“https://list.jd.com/list.html?cat=9987,653,655“);
FilePipeline filePipeline = new FilePipeline();
filePipeline.setPath(“D:/tmp/jd/html”);
// 设置使用pipeline
spider.addPipeline(new ConsolePipeline()).addPipeline(filePipeline);
// 启动爬虫
spider.run();
}
@Override
public void process(Page page) {
Html html = page.getHtml();
// 解析页面所有 链接
// Selectable sel = html.css(“a”,”href”);
// 提取页面所有链接
Selectable links = html.links();
List<a name="DfNXX"></a>
## 4. Scheduler
访问 url 队列, 默认使用的是内存队列. 如果是url数据量大的时候会占用大量内存. 如果 url 过大可以使用文件队列
| 类 | 说明 | 备注 |
| --- | --- | --- |
| DuplicateRemovedScheduler | 抽象类, 提供一些模板方法. | 继承它可以实现自己的功能 |
| QueueScheduler | 使用内存队列保存抓取的URL | |
| PriorityScheduler | 使用带有优先级的内存队列保存抓取的URL | 耗费内存较大, QueueScheduler更大,但是当设置了request.priority之后, 只能够使用 PriorityScheduler 才可以使用优先级生效 |
| FileCacheQueusScheduler | 使用文件保存抓取 URl, 可以在关闭程序并下次启动时, 从之前抓取到的 URL 继续抓取 | 需指定路径, 会建立 .urls.txt 和.cursor.txt 两个文件 |
| RedisScheduler | 使用 Redis 保存抓取队列, 可进行多台机器同时合并抓取 | 需要安装并启动 Redis |
WebMagic 可以对 url 进行去重处理
1. 默认使用的是 hashset 去重,
1. 需要占用大量内从
2. 规模大量可以使用redis去重
1. redis 去重成本可能会高
3. 使用布隆过滤器进行去重
1. 内存小, 速度快, 成本低, 有可能会误判, 添加url后不能够删除
布隆过滤器的使用方法
1. 添加对应jarxml
2. 具体使用java
// 创建一个Schedule, 指定队列使用布隆过滤器去重
QueueScheduler scheduler = new QueueScheduler();
// 指定队列使用布隆过滤器去重, 并指定URL容量
scheduler.setDuplicateRemover(new BloomFilterDuplicateRemover(10000000));
// 使用spider初始化爬虫
Spider spider = Spider.create(new TestWebMagic3());
// 设置起始URL
spider.addUrl(“https://list.jd.com/list.html?cat=9987,653,655“);
FilePipeline filePipeline = new FilePipeline();
filePipeline.setPath(“D:/tmp/jd/html”);
// 设置使用pipeline
spider.addPipeline(new ConsolePipeline()).addPipeline(filePipeline);
// 设置使用 Scheduler对象
spider.setScheduler(scheduler);
// 启动爬虫
spider.run();
<a name="lQvXt"></a>
## 5. Spider 工具类
Spider 工具类, 可以初始化爬虫, 在spider中配置各个组件, 并启动爬虫.
<a name="tWf2f"></a>
# 5. 综合案例
<a name="mbpyH"></a>
# 6. 代理服务器介绍
[免费代理服务器](https://proxy.mimvp.com/free.php)
<a name="i8DpA"></a>
# 7. WebMagic 代理爬取数据java
public clas MyPageProcessor import PageProcessor{
public void process(Page page){
Html html = page.getHtml();
String str = html.get();
page.putField(“str”,str);
}
public Site getSite(){
return Site.me();
}
public static void main(String[] args){
// 创建一个Downloader对象
HttpClientDownload downloader = new HttpClientDownloader();
// 设置代理服务器
downloader.setProxyProvider(SimpleProxyProvider.from(new Proxy(“221.5.80.66”,”3128”)));
// 初始化爬虫程序
Spider spider = Spider.create(new MyPageProcessor());
spider.setDownloader(downloader);
spider.addUrl(“”);
spider.start();
}
}
<a name="eAil8"></a>
# 8. selenium + 无头浏览器抓取数据
selenium 是一个前段测试框架, 可以使用代码来控制浏览器.<br />**无头浏览器**: 没有界面的浏览器, 可是使用无头浏览器去解析JS. 得到一些 JS 动态加载的内容. 常见的有 pantomjs <br />pantomjs使用步骤
1. 安装 pantomjs
1. 向工程中添加 selenium 的 jar 包, pantomjs 的驱动
1. 创建 pantomjs 的配置信息, 配置pantomjs 的路径
1. 指定 pantomjs 的安装路径
1. 创建一个remoteWebDriver对象, 相当于启动浏览器
1. 让 Driver 对象访问路径
1. 从 Driver 对象中取出浏览器渲染后的结果
1. 关闭浏览器xml
```java// 设置参数, 指定pantomjs 安装路径DesiredCapabilities dcaps = new DesiredCapabilities();// 指定 PhantomJS 浏览器位置dcaps.setCapability(PhantomJSDriverService.PHANTOMJS_EXECUTABLE_PATH_PROPERTY,"phantomjs 安装路径");// 创建一个 RemoteWebDriver 对象相当于启动浏览器RemoteWebDriver driver = new PhantomJSDriver(dcaps);// 让 Driver 对象访问一个路径driver.get("https://www.jd.com");// 执行js语句driver.executeScript("window.scrollTo(0,document.body.scrollHeight - 300)");Thread.sleep(3000);// 从 Driver 对象中取出浏览器渲染之后的结果List<WebElement> list = driver.findElementsByClassSelector("css选择器");// 关闭浏览器driver.close();
chrome 无头浏览器使用
- 安装chrome浏览器
- 安装chrome浏览器的驱动. 把chromedriver.exe文件放到chrome.exe所在的目录中
- 编写代码. 创建chrome浏览器的配置信息
- 给予配置信息创建RemoteWebDriver对象
- 使用Driver对象访问一个网站
- 使用driver控制网站的动作
关闭浏览器
public class ChromeTest{public static void main(String[] args){// 创建配置信息, 指定Chrome浏览器安装位置System.setProperty("webdriver.chrome.driver","chrome driver.exe 驱动路径");ChromeOptions chromeOptions = new ChromeOptions();// 设置为 headless 模式(必须)[有些版本设置无法启动浏览器]chromeOptions.addArguments("--headless");// 设置浏览器窗口打开大小(非必须)chromeOptions.addArguments("--window-size=1920,1080");// 基于配置信息创建RemoteWebDriver对象RemoteWebDriver driver = new ChromeDriver(chromeOptions);// 使用 Drier 对象访问一个网站driver.get("https://www.jd.com");// 使用 driver 控制网站的动作(输入框输入手机关键词)driver.findElementByCssSelector("#key").sendKeys("手机");// 点击搜索按钮Thread.sleep(2000);driver.findElementByCssSelector("#search-link").click();Thread.sleep(2000);// 页面滚动到页面下方driver.executeScript("window.scrolTo(0,document.body.scrollHeight - 300)");Thread.sleep(2000);// 取对应节点信息List<WebElement> list = driver.findElementsByCssSelector("li.gl-item");// 关闭浏览器driver.close();}}
案例抓取京东首页页面数据
下载页面 ```java import java.util.List;
import org.openqa.selenium.WebElement; import org.openqa.selenium.chrome.ChromeDriver; import org.openqa.selenium.chrome.ChromeOptions; import org.openqa.selenium.remote.RemoteWebDriver;
import us.codecraft.webmagic.Page; import us.codecraft.webmagic.Request; import us.codecraft.webmagic.Task; import us.codecraft.webmagic.downloader.Downloader; import us.codecraft.webmagic.selector.PlainText;
public class JdDownloader implements Downloader {
private RemoteWebDriver driver;/*** 初始化无头浏览器信息*/public JdDownloader() {// 创建配置信息, 指定Chrome浏览器安装位置System.setProperty("webdriver.chrome.driver", "chrome driver.exe 驱动路径");ChromeOptions chromeOptions = new ChromeOptions();// 设置为 headless 模式(必须)// chromeOptions.addArguments("--headless");// 设置浏览器窗口打开大小(非必须)chromeOptions.addArguments("--window-size=1920,1080");driver = new ChromeDriver(chromeOptions);}/*** 下载页面的业务逻辑操作*/@Overridepublic Page download(Request request, Task task) {// 从request 中获取访问的 urlString url = request.getUrl();try {// 判断是否是下一页动作if (!url.equalsIgnoreCase("https://nextpage.com")) {// 请求 URLdriver.get(url);// 抓取页面元素List<WebElement> list = driver.findElementsByCssSelector("li.gl-item");// 列表页面if (list.size() > 0) {// 页面需要滚动到最下面driver.executeScript("window.scrollTo(0,document.body.scrollHeight - 300)");Thread.sleep(2000);// 从浏览器中获取数据String html = driver.getPageSource();return createPage(html, driver.getCurrentUrl());}// 详情页面String html = driver.getPageSource();return createPage(html, driver.getCurrentUrl());} else {// 翻页页面处理// 从request对象取出附加参数, 要翻页之前的URLString curUrl = request.getExtra("url");// 访问URLdriver.get(curUrl);// 点击翻页按钮, 到第二页driver.findElementByCssSelector("#J_topPage > a.fp-next").click();Thread.sleep(2000);// 让页面滚动到下方, 加载后30条数据driver.executeScript("window.scrollTo(0,document.body.scrollHeight - 300)");Thread.sleep(2000);// 取浏览器渲染的html结果String html = driver.getPageSource();// 封装成page对象返回return createPage(html, driver.getCurrentUrl());}} catch (InterruptedException e) {e.printStackTrace();}return null;}@Overridepublic void setThread(int threadNum) {// TODO: 一般默认就好,不用修改}/*** 将HTML封装成page对象** @param html* @param url* @return*/private Page createPage(String html, String url) {Page page = new Page();page.setRawText(html);page.setUrl(new PlainText(url));page.setRequest(new Request(url));// 设置页面抓取成功page.setDownloadSuccess(true);return page;}
}
初始爬虫```javaimport java.util.ArrayList;import java.util.List;import lombok.AllArgsConstructor;import lombok.Builder;import lombok.Data;import lombok.NoArgsConstructor;import us.codecraft.webmagic.Page;import us.codecraft.webmagic.Request;import us.codecraft.webmagic.Site;import us.codecraft.webmagic.Spider;import us.codecraft.webmagic.processor.PageProcessor;import us.codecraft.webmagic.selector.Html;import us.codecraft.webmagic.selector.Selectable;/*** 无头浏览器爬取数据**/public class JdWebMagic implements PageProcessor {@Overridepublic void process(Page page) {// 判断是不是详情页面Html html = page.getHtml();List<Selectable> nodes = html.css("li.gl-item").nodes();if (nodes.size() > 0) {List<Item> itemList = new ArrayList<>();// 是列表页面, 解析 squ,sku,详情URLfor (Selectable node : nodes) {String sku = node.css("li", "data-sku").get();String spu = node.css("li", "data-spu").get();String img = node.css("li img", "source-data-lazy-img").get();Item item = Item.builder().sku(sku).spu(spu).img(img).build();itemList.add(item);}// 将商品信息传递给pipelinepage.putField("itemList", itemList);// 解析商品详情地址, 并添加到访问队列中// String href = html.css("li.gl-item div.p-img a", "href").get();List<String> urlList = html.css("li.gl-item div.p-img").links().all();page.addTargetRequests(urlList);// 需要翻页处理, 设置一个固定的url: https://nextpage.com, 以request 的附加参数传递Request request = new Request("https://nextpage.com");// 把当前URL添加到request附加参数中request.putExtra("url", page.getUrl().get());return;} else {// 是详情页面parseItemInfo(page);}}@Overridepublic Site getSite() {return Site.me();}// 解析详情页面private void parseItemInfo(Page page) {Html html = page.getHtml();// skuString sku = html.css("div.preview-info > div.left-btns > a.follow.J-follow", "data-id").get();String title = html.css("div.sku-name", "text").get();String price = html.css("span.p-price > span.price", "text").get();String img = html.css("#spec-img", "src").get();String url = page.getUrl().get();Item item = Item.builder().sku(sku).title(title).price(price).img(img).url(url).build();page.putField("item", item);}}@Data@NoArgsConstructor@AllArgsConstructor@Builderclass Item {private String sku;private String spu;private String title;private String img;private String price;private String url;}
保存数据
import java.util.List;import cn.hutool.core.lang.Console;import us.codecraft.webmagic.ResultItems;import us.codecraft.webmagic.Task;import us.codecraft.webmagic.pipeline.Pipeline;/*** 数据持久化*/public class JdPipeline implements Pipeline {@Overridepublic void process(ResultItems resultItems, Task task) {// TODO: 此处只打印输出, 具体保存请// 商品详情数据List<Item> itemInfos = resultItems.get("itemList");Console.log(itemInfos);// 商品数据List<Item> items = resultItems.get("item");Console.log(items);}}
启动爬虫
public static void main(String[] args) {QueueScheduler scheduler = new QueueScheduler();scheduler.setDuplicateRemover(new BloomFilterDuplicateRemover(10000000));Spider spider = Spider.create(new JdWebMagic());spider.setDownloader(new JdDownloader());spider.addPipeline(new JdPipeline());spider.setScheduler(scheduler);spider.thread(1);spider.addUrl("https://search.jd.com/Search?keyword=%E6%89%8B%E6%9C%BA&enc=utf-8&wq=%E6%89%8B%E6%9C%BA&pvid=3d9d1a2fcb344c0bad4cf24030a6ce4f");spider.start();}
chromedriver 驱动下载
下载和当前浏览器版本匹配度的驱动
