1. WebMagic 介绍

WebMagic项目代码分为核心和扩展两部分。核心部分(webmagic-core)是一个精简的、模块化的爬虫实现,而扩展部分则包括一些便利的、实用性的功能。WebMagic的架构设计参照了Scrapy,目标是尽量的模块化,并体现爬虫的功能特点。
这部分提供非常简单、灵活的API,在基本不改变开发模式的情况下,编写一个爬虫。
扩展部分(webmagic-extension)提供一些便捷的功能,例如注解模式编写爬虫等。同时内置了一些常用的组件,便于爬虫开发。
另外WebMagic还包括一些外围扩展和一个正在开发的产品化项目webmagic-avalon。底层依然是HttpClient和jsoup.

WebMagic 核心组件介绍

  1. downloader 下载组件
  2. PageProcessor 页面解析组件(必须自定义)
  3. scheculer 访问队列组件
  4. pipeline 数据持久组件 (默认是输出到控制台)

    2. 入门

  5. 添加对应jar文件
    ```xml

    us.codecraft webmagic-core 0.7.5
us.codecraft webmagic-extension 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 allLinks = links.all(); // 把链接地址添加到访问队列中 page.addTargetRequests(allLinks); // 把页面专递给pipeline, 由pipeline保存到磁盘中 page.putField(“html”, html.get()); } @Override public Site getSite() { return Site.me(); } } <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 com.google.guava guava 30.1.1-jre 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 org.seleniumhq.selenium selenium-java 4.0.0-rc-1

com.codeborne phantomjsdriver 1.4.4

  1. ```java
  2. // 设置参数, 指定pantomjs 安装路径
  3. DesiredCapabilities dcaps = new DesiredCapabilities();
  4. // 指定 PhantomJS 浏览器位置
  5. dcaps.setCapability(PhantomJSDriverService.PHANTOMJS_EXECUTABLE_PATH_PROPERTY,"phantomjs 安装路径");
  6. // 创建一个 RemoteWebDriver 对象相当于启动浏览器
  7. RemoteWebDriver driver = new PhantomJSDriver(dcaps);
  8. // 让 Driver 对象访问一个路径
  9. driver.get("https://www.jd.com");
  10. // 执行js语句
  11. driver.executeScript("window.scrollTo(0,document.body.scrollHeight - 300)");
  12. Thread.sleep(3000);
  13. // 从 Driver 对象中取出浏览器渲染之后的结果
  14. List<WebElement> list = driver.findElementsByClassSelector("css选择器");
  15. // 关闭浏览器
  16. driver.close();

chrome 无头浏览器使用

  1. 安装chrome浏览器
  2. 安装chrome浏览器的驱动. 把chromedriver.exe文件放到chrome.exe所在的目录中
  3. 编写代码. 创建chrome浏览器的配置信息
  4. 给予配置信息创建RemoteWebDriver对象
  5. 使用Driver对象访问一个网站
  6. 使用driver控制网站的动作
  7. 关闭浏览器

    1. public class ChromeTest{
    2. public static void main(String[] args){
    3. // 创建配置信息, 指定Chrome浏览器安装位置
    4. System.setProperty("webdriver.chrome.driver","chrome driver.exe 驱动路径");
    5. ChromeOptions chromeOptions = new ChromeOptions();
    6. // 设置为 headless 模式(必须)[有些版本设置无法启动浏览器]
    7. chromeOptions.addArguments("--headless");
    8. // 设置浏览器窗口打开大小(非必须)
    9. chromeOptions.addArguments("--window-size=1920,1080");
    10. // 基于配置信息创建RemoteWebDriver对象
    11. RemoteWebDriver driver = new ChromeDriver(chromeOptions);
    12. // 使用 Drier 对象访问一个网站
    13. driver.get("https://www.jd.com");
    14. // 使用 driver 控制网站的动作(输入框输入手机关键词)
    15. driver.findElementByCssSelector("#key").sendKeys("手机");
    16. // 点击搜索按钮
    17. Thread.sleep(2000);
    18. driver.findElementByCssSelector("#search-link").click();
    19. Thread.sleep(2000);
    20. // 页面滚动到页面下方
    21. driver.executeScript("window.scrolTo(0,document.body.scrollHeight - 300)");
    22. Thread.sleep(2000);
    23. // 取对应节点信息
    24. List<WebElement> list = driver.findElementsByCssSelector("li.gl-item");
    25. // 关闭浏览器
    26. driver.close();
    27. }
    28. }

    案例抓取京东首页页面数据

    下载页面 ```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 {

  1. private RemoteWebDriver driver;
  2. /**
  3. * 初始化无头浏览器信息
  4. */
  5. public JdDownloader() {
  6. // 创建配置信息, 指定Chrome浏览器安装位置
  7. System.setProperty("webdriver.chrome.driver", "chrome driver.exe 驱动路径");
  8. ChromeOptions chromeOptions = new ChromeOptions();
  9. // 设置为 headless 模式(必须)
  10. // chromeOptions.addArguments("--headless");
  11. // 设置浏览器窗口打开大小(非必须)
  12. chromeOptions.addArguments("--window-size=1920,1080");
  13. driver = new ChromeDriver(chromeOptions);
  14. }
  15. /**
  16. * 下载页面的业务逻辑操作
  17. */
  18. @Override
  19. public Page download(Request request, Task task) {
  20. // 从request 中获取访问的 url
  21. String url = request.getUrl();
  22. try {
  23. // 判断是否是下一页动作
  24. if (!url.equalsIgnoreCase("https://nextpage.com")) {
  25. // 请求 URL
  26. driver.get(url);
  27. // 抓取页面元素
  28. List<WebElement> list = driver.findElementsByCssSelector("li.gl-item");
  29. // 列表页面
  30. if (list.size() > 0) {
  31. // 页面需要滚动到最下面
  32. driver.executeScript("window.scrollTo(0,document.body.scrollHeight - 300)");
  33. Thread.sleep(2000);
  34. // 从浏览器中获取数据
  35. String html = driver.getPageSource();
  36. return createPage(html, driver.getCurrentUrl());
  37. }
  38. // 详情页面
  39. String html = driver.getPageSource();
  40. return createPage(html, driver.getCurrentUrl());
  41. } else {
  42. // 翻页页面处理
  43. // 从request对象取出附加参数, 要翻页之前的URL
  44. String curUrl = request.getExtra("url");
  45. // 访问URL
  46. driver.get(curUrl);
  47. // 点击翻页按钮, 到第二页
  48. driver.findElementByCssSelector("#J_topPage > a.fp-next").click();
  49. Thread.sleep(2000);
  50. // 让页面滚动到下方, 加载后30条数据
  51. driver.executeScript("window.scrollTo(0,document.body.scrollHeight - 300)");
  52. Thread.sleep(2000);
  53. // 取浏览器渲染的html结果
  54. String html = driver.getPageSource();
  55. // 封装成page对象返回
  56. return createPage(html, driver.getCurrentUrl());
  57. }
  58. } catch (InterruptedException e) {
  59. e.printStackTrace();
  60. }
  61. return null;
  62. }
  63. @Override
  64. public void setThread(int threadNum) {
  65. // TODO: 一般默认就好,不用修改
  66. }
  67. /**
  68. * 将HTML封装成page对象
  69. *
  70. * @param html
  71. * @param url
  72. * @return
  73. */
  74. private Page createPage(String html, String url) {
  75. Page page = new Page();
  76. page.setRawText(html);
  77. page.setUrl(new PlainText(url));
  78. page.setRequest(new Request(url));
  79. // 设置页面抓取成功
  80. page.setDownloadSuccess(true);
  81. return page;
  82. }

}

  1. 初始爬虫
  2. ```java
  3. import java.util.ArrayList;
  4. import java.util.List;
  5. import lombok.AllArgsConstructor;
  6. import lombok.Builder;
  7. import lombok.Data;
  8. import lombok.NoArgsConstructor;
  9. import us.codecraft.webmagic.Page;
  10. import us.codecraft.webmagic.Request;
  11. import us.codecraft.webmagic.Site;
  12. import us.codecraft.webmagic.Spider;
  13. import us.codecraft.webmagic.processor.PageProcessor;
  14. import us.codecraft.webmagic.selector.Html;
  15. import us.codecraft.webmagic.selector.Selectable;
  16. /**
  17. * 无头浏览器爬取数据
  18. *
  19. */
  20. public class JdWebMagic implements PageProcessor {
  21. @Override
  22. public void process(Page page) {
  23. // 判断是不是详情页面
  24. Html html = page.getHtml();
  25. List<Selectable> nodes = html.css("li.gl-item").nodes();
  26. if (nodes.size() > 0) {
  27. List<Item> itemList = new ArrayList<>();
  28. // 是列表页面, 解析 squ,sku,详情URL
  29. for (Selectable node : nodes) {
  30. String sku = node.css("li", "data-sku").get();
  31. String spu = node.css("li", "data-spu").get();
  32. String img = node.css("li img", "source-data-lazy-img").get();
  33. Item item = Item.builder().sku(sku).spu(spu).img(img).build();
  34. itemList.add(item);
  35. }
  36. // 将商品信息传递给pipeline
  37. page.putField("itemList", itemList);
  38. // 解析商品详情地址, 并添加到访问队列中
  39. // String href = html.css("li.gl-item div.p-img a", "href").get();
  40. List<String> urlList = html.css("li.gl-item div.p-img").links().all();
  41. page.addTargetRequests(urlList);
  42. // 需要翻页处理, 设置一个固定的url: https://nextpage.com, 以request 的附加参数传递
  43. Request request = new Request("https://nextpage.com");
  44. // 把当前URL添加到request附加参数中
  45. request.putExtra("url", page.getUrl().get());
  46. return;
  47. } else {
  48. // 是详情页面
  49. parseItemInfo(page);
  50. }
  51. }
  52. @Override
  53. public Site getSite() {
  54. return Site.me();
  55. }
  56. // 解析详情页面
  57. private void parseItemInfo(Page page) {
  58. Html html = page.getHtml();
  59. // sku
  60. String sku = html.css("div.preview-info > div.left-btns > a.follow.J-follow", "data-id").get();
  61. String title = html.css("div.sku-name", "text").get();
  62. String price = html.css("span.p-price > span.price", "text").get();
  63. String img = html.css("#spec-img", "src").get();
  64. String url = page.getUrl().get();
  65. Item item = Item.builder().sku(sku).title(title).price(price).img(img).url(url).build();
  66. page.putField("item", item);
  67. }
  68. }
  69. @Data
  70. @NoArgsConstructor
  71. @AllArgsConstructor
  72. @Builder
  73. class Item {
  74. private String sku;
  75. private String spu;
  76. private String title;
  77. private String img;
  78. private String price;
  79. private String url;
  80. }

保存数据

  1. import java.util.List;
  2. import cn.hutool.core.lang.Console;
  3. import us.codecraft.webmagic.ResultItems;
  4. import us.codecraft.webmagic.Task;
  5. import us.codecraft.webmagic.pipeline.Pipeline;
  6. /**
  7. * 数据持久化
  8. */
  9. public class JdPipeline implements Pipeline {
  10. @Override
  11. public void process(ResultItems resultItems, Task task) {
  12. // TODO: 此处只打印输出, 具体保存请
  13. // 商品详情数据
  14. List<Item> itemInfos = resultItems.get("itemList");
  15. Console.log(itemInfos);
  16. // 商品数据
  17. List<Item> items = resultItems.get("item");
  18. Console.log(items);
  19. }
  20. }

启动爬虫

  1. public static void main(String[] args) {
  2. QueueScheduler scheduler = new QueueScheduler();
  3. scheduler.setDuplicateRemover(new BloomFilterDuplicateRemover(10000000));
  4. Spider spider = Spider.create(new JdWebMagic());
  5. spider.setDownloader(new JdDownloader());
  6. spider.addPipeline(new JdPipeline());
  7. spider.setScheduler(scheduler);
  8. spider.thread(1);
  9. 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");
  10. spider.start();
  11. }

chromedriver 驱动下载

chrome driver 驱动下载

下载和当前浏览器版本匹配度的驱动