前言
从大二开始接触python
,到现在已经是第三个年头了;随着入职腾讯,进入云原生行业后,python
已经不再是我的主要开发语言,我转而收养了golang
小地鼠成为了一名gopher
但python
依然是我的工具人好伙伴(日常生活中一旦有自动化的念头也会直接想到python
),并且作为数据工作者,对于python
的数据处理能力还是挺依赖的,golang
的生态也没有好到能面面俱到
鄙人大二时课设写过一个小小的b站爬虫(基于bs4
, re
和selenium
等简单写的),最后也只是草草爬了几十万的用户数据以及几百万的视频数据,做了做没有什么意义的词频分析,而scrapy
作为我一定会忘记的爬虫必会知识,还是有必要写一篇小笔记record
一下的
需要了解的词
- 网络爬虫:泛指获取网页信息,提取有用信息的行为
- selenium: web自动化测试工具集,但在爬虫工程中也经常使用,模拟人的点击操作驱动浏览器来获取网页信息
Scrapy
是什么
Scrapy
是一个为了爬取网站数据,提取结构性数据而编写的应用框架。 可以应用在包括数据挖掘,信息处理或存储历史数据等一系列的程序中。其最初是为了 页面抓取 (更确切来说, 网络抓取 )所设计的, 也可以应用在获取API
所返回的数据(例如 Amazon Associates Web Services
) 或者通用的网络爬虫。也是高层次的屏幕抓取和web抓取框架,用于抓取web站点并从页面中提取结构化的数据。Scrapy
用途广泛,可以用于数据挖掘、监测和自动化测试。
Scrapy
吸引人的地方在于它是一个框架,任何人都可以根据需求方便的修改。它也提供了多种类型爬虫的基类,如BaseSpider,sitemap
爬虫等
架构
Scrapy
使用了 Twisted
异步网络库来处理网络通讯,整体架构大致如下:
各组件的作用
Scrapy Engine
引擎负责控制数据流在系统中所有组件中流动,并在相应动作发生时触发事件。 详细内容查看下面的数据流(Data Flow)部分。
此组件相当于爬虫的“大脑”,是整个爬虫的调度中心
调度器(Scheduler)
调度器从引擎接受request并将他们入队,以便之后引擎请求他们时提供给引擎。
初始的爬取URL和后续在页面中获取的待爬取的URL将放入调度器中,等待爬取。同时调度器会自动去除重复的URL(如果特定的URL不需要去重也可以通过设置实现,如post请求的URL)
下载器(Downloader)
下载器负责获取页面数据并提供给引擎,而后提供给spider
Spiders
Spider是Scrapy用户编写用于分析response并提取item(即获取到的item)或额外跟进的URL的类。 每个spider负责处理一个特定(或一些)网站。
Item Pipeline
Item Pipeline负责处理被spider提取出来的item。典型的处理有清理、 验证及持久化(例如存取到数据库中)
当页面被爬虫解析所需的数据存入Item后,将被发送到项目管道(Pipeline),并经过几个特定的次序处理数据,最后进行数据持久化
下载器中间件(Downloader middlewares)
下载器中间件是在引擎及下载器之间的特定钩子(specific hook),处理Downloader传递给引擎的response。 其提供了一个简便的机制,通过插入自定义代码来扩展Scrapy功能。
通过设置下载器中间件可以实现爬虫自动更换user-agent, IP
等功能
Spider中间件(Spider middlewares)
Spider中间件是在引擎及Spider之间的特定钩子(specific hook),处理spider的输入(response)和输出(items及requests)。 其提供了一个简便的机制,通过插入自定义代码来扩展Scrapy功能。
数据流(Data flow)
scrapy
爬取数据时的数据流如下:
- 引擎打开一个网站(open a domain),找到处理该网站的Spider并向该spider请求第一个要爬取的URL(s)
- 引擎从Spider中获取到第一个要爬取的URL并在调度器(Scheduler)以Request调度
- 引擎向调度器请求下一个要爬取的URL
- 调度器返回下一个要爬取的URL给引擎,引擎将URL通过下载中间件(请求(request)方向)转发给下载器(Downloader)
- 一旦页面下载完毕,下载器生成一个该页面的Response,并将其通过下载中间件(返回(response)方向)发送给引擎
- 引擎从下载器中接收到Response并通过Spider中间件(输入方向)发送给Spider处理。
- Spider处理Response并返回爬取到的Item及(跟进的)新的Request给引擎
- 引擎将(Spider返回的)爬取到的Item给Item Pipeline,将(Spider返回的)Request给调度器
- (从第二步)重复直到调度器中没有更多地request,引擎关闭该网站
hello world in scrapy
创建scrapy
项目
在项目目录下shell执行:
scrapy startproject tutorial
创建后目录结构如下:
tutorial/ scrapy.cfg # deploy configuration file tutorial/ # project's Python module, you'll import your code from here __init__.py items.py # project items definition file middlewares.py # project middlewares file pipelines.py # project pipelines file settings.py # project settings file spiders/ # a directory where you'll later put your spiders __init__.py
定义目标类
Scrapy spider可以以python的dict来返回提取的数据.虽然dict很方便,并且用起来也熟悉,但是其缺少结构性,容易打错字段的名字或者返回不一致的数据,尤其在具有多个spider的大项目中。 为了定义常用的输出数据,Scrapy提供了 Item 类。 Item 对象是种简单的容器,保存了爬取到得数据。 其提供了 类似于词典(dictionary-like) 的API以及用于声明可用字段的简单语法。 许多Scrapy组件使用了Item提供的额外信息: exporter根据Item声明的字段来导出数据、 序列化可以通过Item字段的元数据(metadata)来定义、 trackref 追踪Item实例来帮助寻找内存泄露 (see 使用 trackref 调试内存泄露) 等等。
以我的习惯我喜欢先定好爬取目标,因为爬虫的主要目标就是从非结构性数据源中提取结构性信息,所以这里我们先在items.py
中定义我们的目标数据
# -*- coding: utf-8 -*-
# Define here the models for your scraped items
#
# See documentation in:
# http://doc.scrapy.org/en/latest/topics/items.html
import scrapy
class TutorialItem(scrapy.Item):
# define the fields for your item here like:
# name = scrapy.Field()
title = scrapy.Field()
price = scrapy.Field()
comment = scrapy.Field()
product_id = scrapy.Field()
制作爬虫
制作爬虫,总体来说分为两步:先爬再取
也就是说,首先你要获取整个网页的所有内容,然后再取出其中对你有用的部分
要建立一个Spider
,你必须用scrapy.spider.BaseSpider
创建一个子类,并确定三个强制的属性:
name
:爬虫的识别名称,必须是唯一的,在不同的爬虫中你必须定义不同的名字start_urls
:爬取的URL列表;爬虫从这里开始抓取数据,所以,第一次下载的数据将会从这些urls
开始,其他子URL
将会从这些起始URL中继承性生成parse()
:解析的方法,调用的时候传入从每一个URL
传回的Response
对象作为唯一参数,负责解析并匹配抓取的数据(解析为item
),跟踪更多的URL
常规使用scrapy.Request
来递归地创建Response
进行爬取(这种形式下也可以使用bs4
, xpath
等工具来构建url
):
import scrapy
class QuotesSpider(scrapy.Spider):
name = "quotes"
start_urls = [
'http://quotes.toscrape.com/page/1/',
]
def parse(self, response):
for quote in response.css('div.quote'):
yield {
'text': quote.css('span.text::text').get(),
'author': quote.css('small.author::text').get(),
'tags': quote.css('div.tags a.tag::text').getall(),
}
next_page = response.css('li.next a::attr(href)').get()
if next_page is not None:
next_page = response.urljoin(next_page)
yield scrapy.Request(next_page, callback=self.parse)
其中20, 21
行又可以用response.follow
简化为:
if next_page is not None:
yield response.follow(next_page, callback=self.parse)
也可以直接将Selector
传递给response.follow
:
for href in response.css('li.next a::attr(href)'):
yield response.follow(href, callback=self.parse)
至此我们就得到了我们的目标items
,之后我们可以选择直接输出到文件或者pipelines.py`中做数据清洗 / 验证以及数据的持久化存储了
总结
scrapy
整体看下来是一个完整但偏笨重的爬虫框架,其优势是支持并发,而且集成了 HTTP 请求、下载、解析、调度等爬虫程序中常见的功能模块,让爬虫工程师只专注于页面解析和制定抓取规则
但高度的抽象模块们让整个爬虫项目显得比较臃肿,每个爬虫项目都需要按照相应的模版生成好几个文件,这一点上可以类比django
,可能在一些简单web应用上我就会选择flask
;而对于爬虫来说,基于golang
的colly
就是一个非常轻便的爬虫框架,并发控制等在golang
中也非常简单,在这里埋一个colly
爬虫框架的文章坑吧hh