11

爬虫

简单的说网络爬虫(Web crawler)也叫做网络铲(Web scraper)、网络蜘蛛(Web spider),其行为一般是先“爬”到对应的网页上,再把需要的信息“铲”下来。

分类

网络爬虫按照系统结构和实现技术,大致可以分为以下几种类型:通用网络爬虫(General Purpose Web Crawler)、聚焦网络爬虫(Focused Web Crawler)、增量式网络爬虫(Incremental Web Crawler)、深层网络爬虫(Deep Web Crawler)。实际的网络爬虫系统通常是几种爬虫技术相结合实现的。

通用网络爬虫

通用网络爬虫又称全网爬虫(Scalable Web Crawler),爬取对象从一些种子 URL 扩充到整个 Web。主要为门户站点搜索引擎和大型 Web 服务提供商采集数据。

通用网络爬虫的结构大致可以分为页面爬取模块 、页面分析模块、链接过滤模块、页面存储模块、URL 队列、初始 URL 集合几个部分。为提高工作效率,通用网络爬虫会采取一定的爬取策略。 常用的爬取策略有:深度优先策略、广度优先策略。

1) 深度优先策略(DFS):其基本方法是按照深度由低到高的顺序,依次访问下一级网页链接,直到不能再深入为止。

2) 广度优先策略(BFS):此策略按照网页内容目录层次深浅来爬取页面,处于较浅目录层次的页面首先被爬取。 当同一层次中的页面爬取完毕后,爬虫再深入下一层继续爬取。

聚焦网络爬虫

聚焦网络爬虫(Focused Crawler),又称主题网络爬虫(Topical Crawler),是指选择性地爬取那些与预先定义好的主题相关页面的网络爬虫。 和通用网络爬虫相比,聚焦爬虫只需要爬取与主题相关的页面,极大地节省了硬件和网络资源,保存的页面也由于数量少而更新快,还可以很好地满足一些特定人群对特定领域信息的需求。我们之前爬的歌单就属于这一种。

增量式网络爬虫

增量式网络爬虫(Incremental Web Crawler)是指对已下载网页采取增 量式更新和只爬取新产生的或者已经发生变化网页的爬虫,它能够在一定程度上保证所爬取的页面是尽可能新的页面。 和周期性爬取和刷新页面的网络爬虫相比,增量式爬虫只会在需要的时候爬取新产生或发生更新的页面 ,并不重新下载没有发生变化的页面,可有效减少数据下载量,及时更新已爬取的网页,减小时间和空间上的耗费,但是增加了爬取算法的复杂度和实现难度。现在比较火的舆情爬虫一般都是增量式网络爬虫。

深网爬虫

Web 页面按存在方式可以分为表层网页(Surface Web)和深层网页(Deep Web,也称 Invisible Web Pages 或 Hidden Web)。 表层网页是指传统搜索引擎可以索引的页面,以超链接可以到达的静态网页为主构成的 Web 页面。Deep Web 是那些大部分内容不能通过静态链接获取的、隐藏在搜索表单后的,只有用户提交一些关键词才能获得的 Web 页面。例如那些用户注册后内容才可见的网页就属于 Deep Web。

开源框架

Project Language Star Watch Fork
Nutch Java 1111 195 808
webmagic Java 4216 618 2306
WebCollector Java 1222 255 958
heritrix3 Java 773 141 428
crawler4j Java 1831 242 1136
Pyspider Python 8581 687 2273
Scrapy Python 19642 1405 5261

Nutch

介绍:

Nutch是一个开源的Java实现的搜索引擎。它提供了我们运行自己的搜索引擎所需的全部工具,包括全文搜索和网络爬虫。

尽管搜索是上网的基本要求,但是现有的搜索引擎的数目却在下降。并且这很有可能进一步演变成为一个公司垄断了几乎所有的网页搜索为其谋取商业利益。这显然不利于广大Internet用户。

Nutch为我们提供了这样一个不同的选择。相对于那些商用的搜索引擎,Nutch作为开放源代码的搜索引擎将会更加透明,从而更值得大家信赖。现在所有主要的搜索引擎都采用私有的排序算法, 而不会解释为什么一个网页会排在一个特定的位置。除此之外, 有的搜索引擎依照网站所付的费用, 而不是根据它们本身的价值进行排序。与它们不同,Nutch没有什么需要隐瞒,也没有动机去扭曲搜索的结果。Nutch将尽自己最大的努力为用户提供最好的搜索结果。

优点:

Nutch支持分布式抓取,并有Hadoop支持,可以进行多机分布抓取,存储和索引。另外很吸引人的一点在于,它提供了一种插件框架,使得其对各种网页内容的解析、各种数据的采集、查询、集群、过滤等功能能够方便的进行扩展。正是由于有此框架,使得 Nutch 的插件开发非常容易,第三方的插件也层出不穷,极大的增强了 Nutch 的功能和声誉。

缺点:

对于大多数用户来说,一般是想做一个精确数据爬取的爬虫,就像第一篇里爬歌单那个“主题爬虫”。而第二篇介绍的“通用爬虫”适合做搜索引擎,这种需求就比较少。如果以此为标准,那么为搜索引擎而生的Nutch就有着天生缺点。Nutch的架构里大部分为搜索引擎而设计的,对精确爬取没有特别的考虑。也就是说,用Nutch做主题爬虫,会浪费很多的时间在不必要的计算上。而且如果你试图通过对Nutch进行二次开发来扩展其定制能力,基本上就要破坏Nutch的框架,反而不如自己写了。

Pyspider

介绍:

Pyspider是一个国人编写的强大的网络爬虫系统并带有强大的WebUI。采用Python语言编写,分布式架构,支持多种数据库后端,强大的WebUI支持脚本编辑器,任务监视器,项目管理器以及结果查看器。

Pyspider的主要功能包括,抓取、更新调度多站点的特定的页面;需要对页面进行结构化信息提取;灵活可扩展,稳定可监控。满足了绝大多数Python爬虫的需求 —— 定向抓取,结构化化解析。但是面对结构迥异的各种网站,单一的抓取模式并不一定能满足,灵活的抓取控制是必须的。为了达到这个目的,单纯的配置文件往往不够灵活,于是,通过脚本去控制抓取成为了最后的选择。而去重调度,队列,抓取,异常处理,监控等功能作为框架,提供给抓取脚本,并保证灵活性。最后加上web的编辑调试环境,以及web任务监控,即成为了最终的框架。

优点:

  1. 支持分布式部署。
  2. 完全可视化,对用户非常友好:WEB 界面编写调试脚本,起停脚本,监控执行状态,查看活动历史,获取结果产出。
  3. 简单,五分钟就能上手。脚本规则简单,开发效率高。
  4. 支持抓取JavaScript的页面。

总之,Pyspider非常强大,强大到更像一个产品而不是一个框架。

缺点:

  1. URL去重使用数据库而不是布隆过滤器,亿级存储的db io将导致效率急剧降低。
  2. 使用上的人性化牺牲了灵活度,定制化能力降低。

Scrapy

介绍:

Scrapy是一个为了爬取网站数据,提取结构性数据而编写的应用框架。 可以应用在包括数据挖掘,信息处理或存储历史数据等一系列的程序中。Scrapy 使用 Twisted这个异步网络库来处理网络通讯,架构清晰,并且包含了各种中间件接口,可以灵活的完成各种需求。Scratch,是抓取的意思,这个Python的爬虫框架叫Scrapy,大概也是这个意思吧。

优点:

  1. 极其灵活的定制化爬取。
  2. 社区人数多、文档完善。
  3. URL去重采用布隆过滤器方案。
  4. 可以处理不完整的HTML,Scrapy已经提供了selectors(一个在lxml的基础上提供了更高级的接口),可以高效地处理不完整的HTML代码。

缺点:

  1. 不支持分布式部署。
  2. 原生不支持抓取JavaScript的页面。
  3. 全命令行操作,对用户不友好,需要一定学习周期。

Scrapy入门

架构简介

组件

Engine: 引擎负责控制数据流在系统中所有组件中流动,并在相应动作发生时触发事件。

Scheduler: 调度器从引擎接受Request并将他们入队,以便之后引擎请求他们时提供给引擎。

Downloader: 下载器负责获取页面数据并提供给引擎,而后提供给Spider。

Spiders: Spider是Scrapy用户编写的用于分析Response并提取Item或提取更多需要下载的URL的类。 每个Spider负责处理特定网站。

Item Pipeline: 负责处理被Spider提取出来的Item。典型的功能有清洗、 验证及持久化操作。

Downloader middlewares: 下载器中间件是在Engine及Downloader之间的特定钩子(specific hooks),处理Downloader传递给Engine的Response。 其提供了一个简便的机制,通过插入自定义代码来扩展Scrapy功能。

Spider middlewares: 是在Engine及Spider之间的特定钩子(specific hook),处理Spider的输入(Response)和输出(Items及Requests)。 其提供了一个简便的机制,通过插入自定义代码来扩展Scrapy功能。

数据流

Scrapy中的数据流由执行引擎控制,其过程如下:

  1. Engine从Spider获取第一个需要爬取URL(s)。
  2. Engine用Scheduler调度Requests,并向Scheduler请求下一个要爬取的URL。
  3. Scheduler返回下一个要爬取的URL给Engine。
  4. Engine将URL通过Downloader middlewares转发给Downloader。
  5. 一旦页面下载完毕,下载器生成一个该页面的Response,并将其通过Downloader middlewares发送给Engine。
  6. 引擎从Downloader中接收到Response并通过Spider middlewares发送给Spider处理。
  7. Spider处理Response并返回爬取到的Item及新的Request给Engine。
  8. Engine将爬取到的Item给Item Pipeline,然后将Request给Scheduler。
  9. 从第一步开始重复这个流程,直到Scheduler中没有更多的URLs。

架构就是这样,流程和我第二篇里介绍的迷你架构差不多,但扩展性非常强大。

入门

安装scrapy
pip install scrapy
创建项目

在开始爬取之前,您必须创建一个新的Scrapy项目。 进入您打算存储代码的目录中,运行下列命令:

scrapy startproject tutorial

该命令将会创建包含下列内容的 tutorial 目录:

tutorial/
    scrapy.cfg            # 项目的配置文件
    tutorial/             # 该项目的python模块。之后您将在此加入代码
        __init__.py
        items.py          # 项目中的item文件
        pipelines.py      # 项目中的pipelines文件
        settings.py       # 项目的设置文件
        spiders/          # 放置spider代码的目录
            __init__.py
编写第一个爬虫

Spider是用户编写用于从单个网站(或者一些网站)爬取数据的类。其包含了一个用于下载的初始URL,以及如何跟进网页中的链接以及如何分析页面中的内容的方法。

以下为我们的第一个Spider代码,保存在 tutorial/spiders 目录下的 quotes_spider.py文件中:

import scrapy

class QuotesSpider(scrapy.Spider):
    name = "quotes"

    def start_requests(self):
        urls = [
            'http://quotes.toscrape.com/page/1/',
            'http://quotes.toscrape.com/page/2/',
        ]
        for url in urls:
            yield scrapy.Request(url=url, callback=self.parse)

    def parse(self, response):
        page = response.url.split("/")[-2]
        filename = 'quotes-%s.html' % page
        with open(filename, 'wb') as f:
            f.write(response.body)
        self.log('Saved file %s' % filename)

为了创建一个Spider,你必须继承 scrapy.Spider 类, 且定义以下三个属性:

  • name: 用于区别Spider。 该名字必须是唯一的,您不可以为不同的Spider设定相同的名字。
  • start_urls: 包含了Spider在启动时进行爬取的url列表。 因此,第一个被获取到的页面将是其中之一。 后续的URL则从初始的URL获取到的数据中提取。
  • parse() 是spider的一个方法。 被调用时,每个初始URL完成下载后生成的Response 对象将会作为唯一的参数传递给该函数。 该方法负责解析返回的数据(response data),提取数据以及生成需要进一步处理的URL的 Request 对象。

进入项目的根目录,执行下列命令启动spider:

scrapy s crawl quotes

这个命令启动用于爬取 quotes.toscrape.com 的spider,你将得到类似的输出:

2017-05-10 20:36:17 [scrapy.core.engine] INFO: Spider opened
2017-05-10 20:36:17 [scrapy.extensions.logstats] INFO: Crawled 0 pages (at 0 pages/min), scraped 0 items (at 0 items/min)
2017-05-10 20:36:17 [scrapy.extensions.telnet] DEBUG: Telnet console listening on 127.0.0.1:6023
2017-05-10 20:36:17 [scrapy.core.engine] DEBUG: Crawled (404) <GET http://quotes.toscrape.com/robots.txt> (referer: None)
2017-05-10 20:36:17 [scrapy.core.engine] DEBUG: Crawled (200) <GET http://quotes.toscrape.com/page/1/> (referer: None)
2017-05-10 20:36:17 [scrapy.core.engine] DEBUG: Crawled (200) <GET http://quotes.toscrape.com/page/2/> (referer: None)
2017-05-10 20:36:17 [quotes] DEBUG: Saved file quotes-1.html
2017-05-10 20:36:17 [quotes] DEBUG: Saved file quotes-2.html
2017-05-10 20:36:17 [scrapy.core.engine] INFO: Closing spider (finished)
提取数据

我们之前只是保存了HTML页面,并没有提取数据。现在升级一下代码,把提取功能加进去。至于如何使用浏览器的开发者模式分析网页,之前已经介绍过了。

import scrapy

class QuotesSpider(scrapy.Spider):
    name = "quotes"
    start_urls = [
        'http://quotes.toscrape.com/page/1/',
        'http://quotes.toscrape.com/page/2/',
    ]

    def parse(self, response):
        for quote in response.css('div.quote'):
            yield {
                'text': quote.css('span.text::text').extract_first(),
                'author': quote.css('small.author::text').extract_first(),
                'tags': quote.css('div.tags a.tag::text').extract(),
            }

再次运行这个爬虫,你将在日志里看到被提取出的数据:

2017-05-10 20:38:33 [scrapy.core.scraper] DEBUG: Scraped from <200 http://quotes.toscrape.com/page/1/>
{'tags': ['life', 'love'], 'author': 'André Gide', 'text': '“It is better to be hated for what you are than to be loved for what you are not.”'}
2017-05-10 20:38:33 [scrapy.core.scraper] DEBUG: Scraped from <200 http://quotes.toscrape.com/page/1/>
{'tags': ['edison', 'failure', 'inspirational', 'paraphrased'], 'author': 'Thomas A. Edison', 'text': "“I have not failed. I've just found 10,000 ways that won't work.”"}
保存爬取的数据

最简单存储爬取的数据的方式是使用 Feed exports:

scrapy crawl quotes -o quotes.json

该命令将采用 JSON 格式对爬取的数据进行序列化,生成quotes.json文件。

Pyspider

架构简介

pyspider的架构主要分为 scheduler(调度器), fetcher(抓取器), processor(脚本执行):

  • 各个组件间使用消息队列连接,除了scheduler是单点的,fetcher 和 processor 都是可以多实例分布式部署的。 scheduler 负责整体的调度控制
  • 任务由 scheduler 发起调度,fetcher 抓取网页内容, processor 执行预先编写的python脚本,输出结果或产生新的提链任务(发往 scheduler),形成闭环。
  • 每个脚本可以灵活使用各种python库对页面进行解析,使用框架API控制下一步抓取动作,通过设置回调控制解析动作。

入门

抓取电影的相关信息

安装
pip install pyspider
启动
pyspider

运行成功后用浏览器打开http://localhost:5000/访问控制台

选取url

既然我们要爬所有的电影,首先我们需要抓一个电影列表,一个好的列表应该:

  • 包含足够多的电影的 URL
  • 通过翻页,可以遍历到所有的电影
  • 一个按照更新时间排序的列表,可以更快抓到最新更新的电影

我们在 http://movie.douban.com/ 扫了一遍,发现并没有一个列表能包含所有电影,只能退而求其次,通过抓取分类下的所有的标签列表页,来遍历所有的电影: http://movie.douban.com/tag/

创建项目

在 pyspider 的 dashboard 的右下角,点击 “Create” 按钮

替换 on_start 函数的 self.crawl 的 URL:

@every(minutes=24 * 60)
def on_start(self):
    self.crawl('http://movie.douban.com/tag/', callback=self.index_page)
  • self.crawl 告诉 pyspider 抓取指定页面,然后使用 callback 函数对结果进行解析。
  • @every 修饰器,表示 on_start 每天会执行一次,这样就能抓到最新的电影了。

点击绿色的 run 执行,你会看到 follows 上面有一个红色的 1,切换到 follows 面板,点击绿色的播放按钮:

Tag 列表页

在 tag 列表页 中,我们需要提取出所有的 电影列表页 的 URL。你可能已经发现了,sample handler 已经提取了非常多大的 URL,所有,一种可行的提取列表页 URL 的方法就是用正则从中过滤出来:

import re
...

    @config(age=10 * 24 * 60 * 60)
    def index_page(self, response):
        for each in response.doc('a[href^="http"]').items():
            if re.match("http://movie.douban.com/tag/\w+", each.attr.href, re.U):
                self.crawl(each.attr.href, callback=self.list_page)
  • 由于 电影列表页和 tag列表页长的并不一样,在这里新建了一个 callback 为 self.list_page
  • @config(age=10 24 60 * 60) 在这表示我们认为 10 天内页面有效,不会再次进行更新抓取

由于 pyspider 是纯 Python 环境,你可以使用 Python 强大的内置库,或者你熟悉的第三方库对页面进行解析。不过更推荐使用 CSS选择器

电影列表页

再次点击 run 让我们进入一个电影列表页(list_page)。在这个页面中我们需要提取:

CSS Selector Helper

在 pyspider 中,还内置了一个 CSS Selector Helper,当你点击页面上的元素的时候,可以帮你生成它的 CSS选择器 表达式。你可以点击 Enable CSS selector helper 按钮,然后切换到 web 页面:

开启后,鼠标放在元素上,会被黄色高亮,点击后,所有拥有相同 CSS选择器 表达式的元素会被高亮。表达式会被插入到 python 代码当前光标位置。创建下面的代码,将光标停留在单引号中间:

def list_page(self, response):
    for each in response.doc('').items():

点击一个电影的链接,CSS选择器 表达式将会插入到你的代码中,如此重复,插入翻页的链接:

def list_page(self, response):
    for each in response.doc('HTML>BODY>DIV#wrapper>DIV#content>DIV.grid-16-8.clearfix>DIV.article>DIV>TABLE TR.item>TD>DIV.pl2>A').items():
        self.crawl(each.attr.href, callback=self.detail_page)
    # 翻页
    for each in response.doc('HTML>BODY>DIV#wrapper>DIV#content>DIV.grid-16-8.clearfix>DIV.article>DIV.paginator>A').items():
        self.crawl(each.attr.href, callback=self.list_page)
翻页是一个到自己的 callback 回调
电影详情页

再次点击 run,follow 到详情页。使用 css selector helper 分别添加电影标题,打分和导演:

def detail_page(self, response):
    return {
        "url": response.url,
        "title": response.doc('HTML>BODY>DIV#wrapper>DIV#content>H1>SPAN').text(),
        "rating": response.doc('HTML>BODY>DIV#wrapper>DIV#content>DIV.grid-16-8.clearfix>DIV.article>DIV.indent.clearfix>DIV.subjectwrap.clearfix>DIV#interest_sectl>DIV.rating_wrap.clearbox>P.rating_self.clearfix>STRONG.ll.rating_num').text(),
        "导演": [x.text() for x in response.doc('a[rel="v:directedBy"]').items()],
    }
开始抓取
  1. 使用 run 单步调试你的代码,对于用一个 callback 最好使用多个页面类型进行测试。然后保存。
  2. 回到 Dashboard,找到你的项目
  3. 将 status 修改为 DEBUG 或 RUNNING
  4. 按 run 按钮

反反爬虫技术

越来越多的网站具有反爬虫特性,有的用图片隐藏关键数据,有的使用反人类的验证码等

爬虫:

爬虫的根本就是得到一个网页的源代码数据。更深入一些,就会出现和网页进行POST交互从而获取服务器接收POST请求后返回的数据!总结:爬虫就是由计算机自动与服务器交互获取数据的工具。(爬虫请注意网站的Robot.txt文件!不要让爬虫违法!也不要让爬虫对网站造成伤害!)

反爬及反反爬概念:

基于服务器资源,保护数据等,很多网站是限制了爬虫效果。那么由人来充当爬虫的角色时,我们怎么获取网页源代码?大部分都是右键源文件。那么有些网站屏蔽了右键,又该怎么办?基本常识,当然是按我们的F12了

在把人当作爬虫时,网页屏蔽右键就是反爬取措施,F12就是我们反反爬取的方式!

网站: https://github.com/luyishisi/...

例子

例子在

https://pan.baidu.com/disk/ho...


lemon
293 声望10 粉丝

只要你golang