爬虫程序编写与常见问题解决办法~

基础知识

工欲善其事,必先利其器,要编写爬虫程序,首先必须找一个爬虫框架,如果你使用Python语言,可以选用scrapy,如果你使用Java语言,可选用WebMagic,本文使用后者,编写爬虫程序无非分以下几步:

  1. 根据URL下载网页,得到HTML(注意并不是通过开发工具看到的HTML,而是网页源代码HTML,这两者有本质区别);
  2. 根据HTML解析您所需要的数据,可以利用xpath获取DOM节点内容或属性值;
  3. 有可能还需要根据得到的HTML解析出其他链接,利用多线程继续爬取;
  4. 解析后的数据存储(数据库,文件等);

WebMagic爬虫框架在core代码中主要有四个模块:Downloader、PageProcessor、Scheduler、Pipeline,分别处理下载,页面解析,管理(管理待抓取的URL,做一些去重工作,默认使用内存队列管理URL,也可以使用Redis进行分布式管理)和持久化工作,因为最终解析出的结构化数据应该是要入库或入文件存储的。

下构图如下

页面下载

WebMagic爬虫框架负责从互联网上下载页面,以便后续处理。WebMagic默认使用了Apache HttpClient作为下载工具,当然你可以使用chromedriver下载动态内容,那么我们应该调用Spider的setDownloader方法spider.setDownloader(new SeleniumDownloader())

页面解析

PageProcessor负责解析页面,抽取有用信息,以及发现新的链接。WebMagic使用Jsoup作为HTML解析工具,并基于其开发了解析XPath的工具Xsoup

这是您需要花大力气编写代码的地方,根据chrome开发工具查看您所需抓取的数据的xpath路径,利用xpath获取节点信息获取数据。

Scheduler

Scheduler负责管理待抓取的URL,以及一些去重的工作。WebMagic默认提供了JDK的内存队列来管理URL,并用集合来进行去重。也支持使用Redis进行分布式管理。

除非项目有一些特殊的分布式需求,否则无需自己定制Scheduler。

Pipeline

Pipeline负责抽取结果的处理,包括计算、持久化到文件、数据库等。WebMagic默认提供了“输出到控制台”和“保存到文件”两种结果处理方案,如果你需要将数据存储到数据库,您可能需要自定义一个Pipeline,以JSON形式保存到文件D:\webmagic\可以使用:

Spider.create(new XXXPageProcessor())
            .addUrl("http://www.baidu.com")
            .addPipeline(new JsonFilePipeline("D:\\webmagic\\"))
            .thread(5)
            .run();

另外,Request对象用于在PageProcessorprocess实现函数中,将其他url的抓取任务扔给Downloader组件去执行,Downloader组件下载到一个页面是Page对象,在此可以获取网页源码内容,可以做xpath解析等,ResultItems相当于一个Map,保存PageProcessor处理的结果,供Pipeline使用,你可以将里面的内容保存到注入数据库中。

Selectable抽取元素

解析处理HTML获取自己想要的数据可能是编写爬虫最麻烦的一环,使用Selectable接口,你可以直接完成页面元素的链式抽取,也无需去关心抽取的细节,比如上文提到的Page.getHtml()得到的就是一个Html对象,实现了Selectable接口。以下是常用的解析方法:

  • xpath(String xpath):根据xpath路径获取节点,如html.xpath("//div[@class='title']/text()",获取包含title类的div包裹的内容;
  • (String selector):根据选择符获取节点,如html.("div.title"),获取包含title类名的div节点列表
  • css(String selector):功能同$,使用css选择器;
  • links():获取所有链接
  • regex(String regex):根据正则匹配出内容
  • replace(String regex,String replacement):替换内容

以上函数都返回Selectable接口,支持链式调用,但想要获取解析后数据的结果时,可以利用get()返回一条String结果或all返回所有结果List<String>match返回匹配结果。

常见问题

在编写爬虫程序时,我们经常会碰到的一些问题汇总与解决方案,帮您快速定位解决问题。

通过代理上网解决IP被封问题

有时候抓取的站点会封我们的IP,公司的外网IP又是固定的,我们可以通过ADSL拨号的方式接入另一个网络,在ADSL网络的服务器上搭建代理服务器,爬虫程序所在的服务器通过代理该台服务器上网,这样再也不怕对方站点封您的IP了,让爬虫程序通过代理爬取网页,代码如下:

HttpClientDownloader downloader= new HttpClientDownloader();
downloader.setProxyProvider(SimpleProxyProvider.from(new Proxy("IP",PORT,"username","password")));
spider.setDownloader(httpClientDownloader)

服务器端返回403错误

有些网站会有一些爬虫的识别机制,影响您利用爬虫去爬数据时,会碰到服务端的403拒绝访问错误,这时,我们应该根据实际访问网址的情况,构造userAgent,Cookie等数据,以便达到可访问网站的目的,典型的应用场景是爬取Discuz!论坛的列表数据,已爬取宁波当地的房产论坛列表为例,利用WebMagic编写的爬虫代码如下:

package test.springboot2.webmagic;

import java.util.List;
import us.codecraft.webmagic.Page;
import us.codecraft.webmagic.Site;
import us.codecraft.webmagic.Spider;
import us.codecraft.webmagic.processor.PageProcessor;

public class TestBBS {
    public static void main(String[] args) {
                Spider.create(new PageProcessor() {
                    private Site site = Site.me().setCharset("GBK").setRetryTimes(3)
                            .addCookie("7nPA_2132_atarget", "1")
                            .addCookie("7nPA_2132_forum_lastvisit", "D_57_1544683126")
                            .addCookie("7nPA_2132_lastact", "1544683156%09forum.php%09ajax")
                            .addCookie("7nPA_2132_lastvisit", "1544679421")
                            .addCookie("7nPA_2132_saltkey", "Y41kZTOe")
                            .addCookie("7nPA_2132_sendmail", "1")
                            .addCookie("7nPA_2132_sid", "HpKlUl")
                            .addCookie("7nPA_2132_st_t", "0%7C1544683126%7Ca40d86281e77b87595a73a73f8a8d6ab")
                            .addCookie("7nPA_2132_visitedfid", "57")
                            .addHeader("Accept","text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8")
                            .addHeader("Accept-Encoding","gzip, deflate, br")
                            .addHeader("Accept-Language","zh-CN,zh;q=0.9")
                            .addHeader("Cache-Control","max-age=0")
                            .addHeader("Connection","keep-alive")
                            .addHeader("Cookie","7nPA_2132_saltkey=Y41kZTOe; 7nPA_2132_lastvisit=1544679421; 7nPA_2132_st_t=0%7C1544683021%7C8f3386d0af3b92c0e84bee8dce32f193; 7nPA_2132_atarget=1; 7nPA_2132_forum_lastvisit=D_57_1544683021; 7nPA_2132_visitedfid=57; 7nPA_2132_sendmail=1; 7nPA_2132_sid=NXsz2s; 7nPA_2132_lastact=1544683081%09forum.php%09ajax")
                            .addHeader("DNT","1")
                            .addHeader("Referer","https://bbs.cnnb.com.cn/member.php?mod=logging&action=logout&formhash=7663dd1d")
                            .setUserAgent("Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.98 Safari/537.36")
                            .setSleepTime(1000);
                    @Override
                    public void process(Page page) {
                        List<String> titles = page.getHtml().xpath("//a[@class='s xst']/text()").all();
                        page.putField("title",titles);
                    }
                    
                    @Override
                    public Site getSite() {
                        return site;
                    }
                })
                .addUrl("http://bbs.cnnb.com.cn/forum.php?mod=forumdisplay&fid=57")
                .thread(1).start();
    }
}

爬取Ajax动态数据

有些网站采用异步Ajax的方式获取列表数据,这些列表数据我们无法直接通过下载网址的文件得到,需要利用虚拟浏览器技术,以ChromeDriver为例,利用selenium-java编写的爬虫程序如下:

import java.text.DateFormat;
import java.text.SimpleDateFormat;

import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.chrome.ChromeDriver;

import us.codecraft.webmagic.Page;
import us.codecraft.webmagic.Site;
import us.codecraft.webmagic.Spider;
import us.codecraft.webmagic.downloader.selenium.SeleniumDownloader;
import us.codecraft.webmagic.processor.PageProcessor;
import us.codecraft.webmagic.selector.Html;

public class TestGetStockList{
    public static void main(String[] args) {
    System.setProperty("selenuim_config","D:\\workspace_spider\\webmagic\\webmagic-selenium\\src\\test\\resources\\config.ini");
        Spider.create(new PageProcessor() {
            private Site site = Site.me().setCharset("utf-8").setRetryTimes(3).setSleepTime(1000);
            @Override
            public void process(Page page) {
                WebDriver driver = new ChromeDriver();
                driver.get("http://www.sse.com.cn/assortment/stock/list/info/company/index.shtml?COMPANY_CODE=600000");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                WebElement webElement = driver.findElement(By.id("tableData_stockListCompany"));
                String str = webElement.getAttribute("outerHTML");
                System.out.println(str);

                Html html = new Html(str);
                System.out.println(html.xpath("//tbody/tr").all());
                String companyCode = html.xpath("//tbody/tr[1]/td/text()").get();

                DateFormat format = new SimpleDateFormat("yyyy-MM-dd");
                String dateString = html.xpath("//tbody/tr[3]/td/text()").get().split("/")[0];

                String stockCode = html.xpath("//tbody/tr[2]/td/text()").get().split("/")[0];
                String name = html.xpath("//tbody/tr[5]/td/text()").get().split("/")[0];
                String department = html.xpath("//tbody/tr[14]/td/text()").get().split("/")[0];
                System.out.println(companyCode);
                System.out.println(stockCode);
                System.out.println(name);
                System.out.println(department);
                driver.close();
            }
            
            @Override
            public Site getSite() {
                return site;
            }
        }).thread(5)
                .addUrl("http://www.sse.com.cn/assortment/stock/list/info/company/index.shtml?COMPANY_CODE=600000")
                .run();
    }
}

注:如果要让代码运行成功需要下载一个chromedriver,如果你是windows可以去这个网址去下https://chromedriver.storage.googleapis.com/2.25/chromedriver_win32.zip,虽然是32位的但是64位也可以用,如果不行的话或者你是其他OS,可以去官网下https://chromedriver.storage.googleapis.com/index.html?path=2.27/

https站点问题

有些https站点只支持TLS1.2,比如大名鼎鼎的Github网站,这时你需要修改框架源码,找到如下类:


image.png

去除如下代码的红框部分:


image.png

否则将无法爬虫https站点数据。

处理非Get请求的爬虫

有时候,我们需要爬取非Get请求的数据,代码如下(其他诸如DELETE,PUT等请求方式类似):

Request req = new Request("http://www.baidu.com"); 
req.setMethod(HttpConstant.Method.POST)
    //也可以采用form提交的方式.HttpRequestBody.form(map)
    .setRequestBody(HttpRequestBody.json("{'id':1}","utf-8"));
spider.addRequest(req);

典型案例

抓取列表+详解

这里我们以作者的新浪博客http://blog.sina.com.cn/flashsword20作为例子。在这个例子里,我们要从最终的博客文章页面,抓取博客的标题、内容、日期等信息,也要从列表页抓取博客的链接等信息,从而获取这个博客的所有文章。

在这个例子中,我们的主要使用几个方法:

  • 从页面指定位置发现链接,使用正则表达式来过滤链接.
  • 在PageProcessor中处理两种页面,根据页面URL来区分需要如何处理。
package test.springboot2.webmagic;

import us.codecraft.webmagic.Page;
import us.codecraft.webmagic.Site;
import us.codecraft.webmagic.Spider;
import us.codecraft.webmagic.processor.PageProcessor;

/**
 * @author code4crafter@gmail.com <br>
 */
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_POST = "http://blog\\.sina\\.com\\.cn/s/blog_\\w+\\.html";

    private Site site = Site
            .me()
            .setDomain("blog.sina.com.cn")
            .setSleepTime(3000)
            .setUserAgent(
                    "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_7_2) AppleWebKit/537.31 (KHTML, like Gecko) Chrome/26.0.1410.65 Safari/537.31");

    @Override
    public void process(Page page) {
        //列表页
        if (page.getUrl().regex(URL_LIST).match()) {
            page.addTargetRequests(page.getHtml().xpath("//div[@class=\"articleList\"]").links().regex(URL_POST).all());
            page.addTargetRequests(page.getHtml().links().regex(URL_LIST).all());
            //文章页
        } else {
            page.putField("title", page.getHtml().xpath("//div[@class='articalTitle']/h2"));
            page.putField("content", page.getHtml().xpath("//div[@id='articlebody']//div[@class='articalContent']"));
            page.putField("date",
                    page.getHtml().xpath("//div[@id='articlebody']//span[@class='time SG_txtc']").regex("\\((.*)\\)"));
        }
    }

    @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")
          .run();
    }
}

后记

基于以上爬虫思路,如果让你设计一个爬虫管理控制中心,你该如何设计与实现呢?请关注我们后续文章,感谢阅读!

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,772评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,458评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,610评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,640评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,657评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,590评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,962评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,631评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,870评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,611评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,704评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,386评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,969评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,944评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,179评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,742评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,440评论 2 342

推荐阅读更多精彩内容

  • 33款可用来抓数据的开源爬虫软件工具 要玩大数据,没有数据怎么玩?这里推荐一些33款开源爬虫软件给大家。 爬虫,即...
    visiontry阅读 7,276评论 1 99
  • 你爬了吗? 要玩大数据,没有数据怎么玩?这里推荐一些33款开源爬虫软件给大家。 爬虫,即网络爬虫,是一种自动获取网...
    Albert新荣阅读 2,218评论 0 8
  • 这个项目也是初窥python爬虫的一个项目,也是我的毕业设计,当时选题的时候,发现大多数人选择的都是网站类,实在是...
    梦航韩语阅读 2,981评论 2 37
  • WebMagic指北 一、快速开始 WebMagic主要包含两个jar包:webmagic-core-{versi...
    thorhill阅读 1,886评论 0 7
  • 上网原理 1、爬虫概念 爬虫是什麽? 蜘蛛,蛆,代码中,就是写了一段代码,代码的功能从互联网中提取数据 互联网: ...
    riverstation阅读 8,026评论 1 2