爬虫高级操作:Scrapy framework
章节内容
- scrapy概述
- scrapy安装
- quick start 入门程序
- 核心API
- scrapy shell
- 深度爬虫
- 请求和响应
- 中间件——下载中间件
- 常见设置操作
课程内容
1. scrapy 概述
官方网站:https://scrapy.org/,打开官方网站,可以看到一段关于scrapy的描述
An open source and collaborative framework for extracting the data you need from websites.
In a fast, simple, yet extensible way.
Scrapy is an application framework for crawling web sites and
extracting structured data which can be used for a wide range
of useful applications, like data mining, information processing
or historical archival.
Even though Scrapy was originally designed for web scraping,
it can also be used to extract data using APIs (such as Amazon
Associates Web Services) or as a general purpose web crawler.
Scrapy是一个为了爬取网站数据,提取结构性数据而编写的应用框架。 可以应用在包括数据挖掘,信息处理或存储历史数据等一系列的程序中。
其最初是为了 页面抓取 (更确切来说, 网络抓取 )所设计的, 也可以应用在获取API所返回的数据(例如 Amazon Associates Web Services ) 或者通用的网络爬虫。
2. scrapy 安装
首先,确认我们的电脑上已经安装了如下程序:
- python2.7.+:windows直接安装配置,ubuntu内置
- [pip and setuptools] or [easy_install]:windows安装包内置,ubuntu需要单独安装
- lxml:一般linux系统中的ubuntu内置了,windows需要单独安装
- OpenSSL:windows之外的系统默认自带
运行命令执行安装
pip install scrapy
在windows中,需要单独安装调用win32的模块,执行如下命令安装
pip install pypiwin32
2. scrapy 入门程序
这一部分主要内容如下
- 创建一个scrapy项目
- 定义提取数据的数据Item
- 编写采集数据的爬虫程序
- 定义Pipline存储提取到的数据
(1) 创建scrapy项目
执行下面的命令,创建第一个基于scrapy框架的爬虫项目
scrapy startproject myspider
该命令会在当前目录下创建如下的文件结构
|-- myspider
|-- scrapy.cfg
|-- myspider/
|-- __init__.py
|-- items.py
|-- pipeline.py
|-- settings.py
|-- spiders/
|-- __init__.py
...
文件详细信息:
-
scrapy.py
:爬虫项目公共配置文件 -
myspider
:爬虫项目的python模块,以后的代码开发就在这个文件夹中 -
myspider/items.py
:项目中的定义数据的模块item -
myspider/pipeline.py
:项目中数据存储模块pipeline -
myspider/settings.py
:项目的设置文件 -
myspider/spiders/..
:项目中存放爬虫程序的文件夹
(2) 定义采集数据对象:Item
Item是用来保存爬取到数据的容器,是一个like dict对象,使用方式和python中的字典大同小异,scrapy提供了额外的保护机制避免出现拼写错误出现的字段未定义异常。
Item类型的创建可以基于scrapy.Item进行构建,然后通过scrapy.Field()构建类型的属性,完成对采集数据的描述
首先根据需要从指定网站[智联招聘]获取到的数据对item进行对象ZhilianItem的创建,然后通过提取的数据[招聘岗位、薪水、发布公司]通过scrapy.Field()来构建类型的属性,编辑myspider/items.py内容如下:
# coding:utf-8
import scrapy
class ZhilianItem(scrapy.Item):
'''
基于scrapy.Item类型定义存储智联招聘数据的模型类
'''
# 定义采集数据的属性字段
job_name = scrapy.Field()
salary = scrapy.Field()
company = scrapy.Field()
通过类型对采集的数据进行封装,开始入门就如同开始学习面向对象定义类型一样,会感觉比较复杂,但是通过类型的封装,可以统一进行数据管理,同时scrapy提供了更多的功能可以通过Item类型直接操作,爬虫操作更加简捷方便!
(3)编写第一个爬虫ZhilianSpider
spider爬虫程序是开发人员编写的用于从指定网站提取数据的类型
爬虫类中会包含一个用于爬取数据的初始url地址,以及深度提取网页中超链接的规则用于分析网页中的内容,同时定义了提取生成Item的方法
通过继承scrapy.Spider可以很方便的构建一个爬虫处理类,类型中要包含如下三个属性:
- name:爬虫程序的名称,在一个scrapy项目中可能会存在多个爬虫程序,名称主要用于区别不同的爬虫程序
- start_urls:包含了爬虫程序启动时进行爬取的url列表,第一个采集的网页是从其中的某个url中直接获取,后续的url则是从初始url获取到的数据中提取
- parse():爬虫的核心处理函数,在程序执行时自动调用,每个初始url完成下载后,自动封装成response对象传递给parse()函数,函数中负责解析采集到的数据response.data,提取数据封装成Item对象以及筛选进一步需要处理的url地址
创建[智联招聘]爬虫程序:myspider/spiders/zhilianspider.py
# coding:utf-8
# 引入scrapy模块
import scrapy
class ZhilianSpider(scrapy.Spider):
'''
智联招聘爬虫程序
'''
# 定义属性
name = "zlspider"
# 定义域名限制
allowed_domains = ['zhaopin.com']
# 定义起始url地址
start_urls = [
'http://sou.zhaopin.com/jobs/searchresult.ashx?jl=%E5%8C%97%E4%BA%AC&kw=django&sm=0&sg=41c5ff15fda04534b7e455fa88794f18&p=1',
'http://sou.zhaopin.com/jobs/searchresult.ashx?jl=%E5%8C%97%E4%BA%AC&kw=django&sm=0&sg=41c5ff15fda04534b7e455fa88794f18&p=2',
'http://sou.zhaopin.com/jobs/searchresult.ashx?jl=%E5%8C%97%E4%BA%AC&kw=django&sm=0&sg=41c5ff15fda04534b7e455fa88794f18&p=3',
'http://sou.zhaopin.com/jobs/searchresult.ashx?jl=%E5%8C%97%E4%BA%AC&kw=django&sm=0&sg=41c5ff15fda04534b7e455fa88794f18&p=4',
'http://sou.zhaopin.com/jobs/searchresult.ashx?jl=%E5%8C%97%E4%BA%AC&kw=django&sm=0&sg=41c5ff15fda04534b7e455fa88794f18&p=5',
]
# 定义采集数据的函数
def parse(self, response):
# 保存数据
filename = response.url.split("&")[-1] + ".html"
with open(filename, "w") as f:
f.write(response.body)
接下来,进入爬虫根目录,执行下面的命令运行爬虫程序
scrapy crawl zlspider
出现如下的信息
(python2_lib) D:\resp_work\py_1709\back_cursor\S-scrapy\myspider>scrapy crawl zlspider
# 程序开始启动~Scrapy 1.5.0 started
2018-01-15 18:09:15 [scrapy.utils.log] INFO: Scrapy 1.5.0 started (bot: myspider)
2018-01-15 18:09:15 [scrapy.utils.log] INFO: Versions: lxml 4.1.1.0, libxml2 2.9.5, cssselect 1.0.3, parsel 1.3.1, w3lib 1.18.0, Twisted
17.9.0, Python 2.7.13 (v2.7.13:a06454b1afa1, Dec 17 2016, 20:53:40) [MSC v.1500 64 bit (AMD64)], pyOpenSSL 17.5.0 (OpenSSL 1.1.0g 2 No
v 2017), cryptography 2.1.4, Platform Windows-10-10.0.16299
# 加载配置操作
2018-01-15 18:09:15 [scrapy.crawler] INFO: Overridden settings: {'NEWSPIDER_MODULE': 'myspider.spiders', 'SPIDER_MODULES': ['myspider.sp
iders'], 'ROBOTSTXT_OBEY': True, 'BOT_NAME': 'myspider'}
2018-01-15 18:09:15 [scrapy.middleware] INFO: Enabled extensions:
['scrapy.extensions.logstats.LogStats',
'scrapy.extensions.telnet.TelnetConsole',
'scrapy.extensions.corestats.CoreStats']
# 启用下载中间件内置功能
2018-01-15 18:09:16 [scrapy.middleware] INFO: Enabled downloader middlewares:
['scrapy.downloadermiddlewares.robotstxt.RobotsTxtMiddleware',
'scrapy.downloadermiddlewares.httpauth.HttpAuthMiddleware',
'scrapy.downloadermiddlewares.downloadtimeout.DownloadTimeoutMiddleware',
'scrapy.downloadermiddlewares.defaultheaders.DefaultHeadersMiddleware',
'scrapy.downloadermiddlewares.useragent.UserAgentMiddleware',
'scrapy.downloadermiddlewares.retry.RetryMiddleware',
'scrapy.downloadermiddlewares.redirect.MetaRefreshMiddleware',
'scrapy.downloadermiddlewares.httpcompression.HttpCompressionMiddleware',
'scrapy.downloadermiddlewares.redirect.RedirectMiddleware',
'scrapy.downloadermiddlewares.cookies.CookiesMiddleware',
'scrapy.downloadermiddlewares.httpproxy.HttpProxyMiddleware',
'scrapy.downloadermiddlewares.stats.DownloaderStats']
# 启用爬虫中间件内置功能
2018-01-15 18:09:16 [scrapy.middleware] INFO: Enabled spider middlewares:
['scrapy.spidermiddlewares.httperror.HttpErrorMiddleware',
'scrapy.spidermiddlewares.offsite.OffsiteMiddleware',
'scrapy.spidermiddlewares.referer.RefererMiddleware',
'scrapy.spidermiddlewares.urllength.UrlLengthMiddleware',
'scrapy.spidermiddlewares.depth.DepthMiddleware']
# 启用Pipeline内置功能
2018-01-15 18:09:16 [scrapy.middleware] INFO: Enabled item pipelines:
[]
# 爬虫程序启动
2018-01-15 18:09:16 [scrapy.core.engine] INFO: Spider opened
2018-01-15 18:09:16 [scrapy.extensions.logstats] INFO: Crawled 0 pages (at 0 pages/min), scraped 0 items (at 0 items/min)
2018-01-15 18:09:16 [scrapy.extensions.telnet] DEBUG: Telnet console listening on 127.0.0.1:6023
2018-01-15 18:09:16 [scrapy.downloadermiddlewares.redirect] DEBUG: Redirecting (302)
# 开始采集数据
to <GET http://sou.zhaopin.com/FileNotFound.htm> fr
om <GET http://sou.zhaopin.com/robots.txt>
2018-01-15 18:09:16 [scrapy.core.engine] DEBUG: Crawled (200) <GET http://sou.zhaopin.com/FileNotFound.htm> (referer: None)
2018-01-15 18:09:16 [scrapy.core.engine] DEBUG: Crawled (200) <GET http://sou.zhaopin.com/jobs/searchresult.ashx?jl=%E5%8C%97%E4%BA%AC&k
w=django&sm=0&sg=41c5ff15fda04534b7e455fa88794f18&p=5> (referer: None)
2018-01-15 18:09:17 [scrapy.core.engine] DEBUG: Crawled (200) <GET http://sou.zhaopin.com/jobs/searchresult.ashx?jl=%E5%8C%97%E4%BA%AC&k
w=django&sm=0&sg=41c5ff15fda04534b7e455fa88794f18&p=1> (referer: None)
2018-01-15 18:09:17 [scrapy.core.engine] DEBUG: Crawled (200) <GET http://sou.zhaopin.com/jobs/searchresult.ashx?jl=%E5%8C%97%E4%BA%AC&k
w=django&sm=0&sg=41c5ff15fda04534b7e455fa88794f18&p=2> (referer: None)
2018-01-15 18:09:17 [scrapy.core.engine] DEBUG: Crawled (200) <GET http://sou.zhaopin.com/jobs/searchresult.ashx?jl=%E5%8C%97%E4%BA%AC&k
w=django&sm=0&sg=41c5ff15fda04534b7e455fa88794f18&p=4> (referer: None)
2018-01-15 18:09:17 [scrapy.core.engine] DEBUG: Crawled (200) <GET http://sou.zhaopin.com/jobs/searchresult.ashx?jl=%E5%8C%97%E4%BA%AC&k
w=django&sm=0&sg=41c5ff15fda04534b7e455fa88794f18&p=3> (referer: None)
2018-01-15 18:09:17 [scrapy.core.engine] INFO: Closing spider (finished)
# 回显采集状态
2018-01-15 18:09:17 [scrapy.statscollectors] INFO: Dumping Scrapy stats:
{'downloader/request_bytes': 2019,
'downloader/request_count': 7,
'downloader/request_method_count/GET': 7,
'downloader/response_bytes': 241042,
'downloader/response_count': 7,
'downloader/response_status_count/200': 6,
'downloader/response_status_count/302': 1,
'finish_reason': 'finished',
'finish_time': datetime.datetime(2018, 1, 15, 10, 9, 17, 674000),
'log_count/DEBUG': 8,
'log_count/INFO': 7,
'response_received_count': 6,
'scheduler/dequeued': 5,
'scheduler/dequeued/memory': 5,
'scheduler/enqueued': 5,
'scheduler/enqueued/memory': 5,
'start_time': datetime.datetime(2018, 1, 15, 10, 9, 16, 319000)}
2018-01-15 18:09:17 [scrapy.core.engine] INFO: Spider closed (finished)
另外我们在爬虫程序所在的目录中,也看到对应的所有start_urls中包含的url地址所在的网页全部被爬虫采集到了本地。
那么接下来,就是通过指定的方式筛选数据,将数据封装在Item中进行后续的处理,scrapy提供了各种选择器可以方便的在response.data中进行数据的提取,官方推荐也是项目中经常出现的选择器如下
- xpath(): 传入xpath表达式,返回xpath所对应的节点的select list列表
- css(): 传入css表达式,返回表达式所对应的节点列表
- extract(): 序列化节点并返回unicode字符串
- re(): 传入正则表达式,进行数据的提取,返回unicode字符串的list列表
注意:CSS vs XPath: 您可以仅仅使用CSS Selector来从网页中 提取数据。不过, XPath提供了更强大的功能。其不仅仅能指明数据所在的路径, 还能查看数据: 比如,您可以这么进行选择: 包含文字 ‘Next Page’ 的链接 。 正因为如此,即使您已经了解如何使用 CSS selector, 我们仍推荐您使用XPath。
接下来,我们修改myspider/spiders.py/ZhilianSpider爬虫程序,通过xpath提取Item中需要的数据
def parse(self, response):
# 定义保存数据的列表
items = []
for each in response.xpath("//div[@class='zhaopin']"):
# 将我们得到的数据封装到一个 `ZhaopinItem` 对象
item = ZhaopinItem()
#extract()方法返回的都是unicode字符串
job_name = each.xpath("p[1]/text()").extract()
salary = each.xpath("p[2]/text()").extract()
company = each.xpath("p[3]/text()").extract()
#xpath返回的是包含一个元素的列表
item['job_name'] = job_name[0]
item['salary'] = salary[0]
item['company'] = company[0]
items.append(item)
# 直接返回最后的所有数据
return items
可以通过如下命令将数据在任意时候导出成想要的结果:
# json格式,默认为Unicode编码
scrapy crawl zlspider -o job.json
# json lines格式,默认为Unicode编码
scrapy crawl zlspider -o job.jsonl
# csv 逗号表达式,可用Excel打开
scrapy crawl zlspider -o job.csv
# xml格式
scrapy crawl zlspider -o job.xml
同时可以将数据直接通过协程的方式交给pipeline进行后续的数据筛选、验证或者存储数据的操作
from items import ZhaopinItem
..
def parse(self, response):
for each in response.xpath("//div[@class='zhaopin']"):
# 将我们得到的数据封装到一个 `ZhaopinItem` 对象
item = ZhaopinItem()
#extract()方法返回的都是unicode字符串
job_name = each.xpath("p[1]/text()").extract()
salary = each.xpath("p[2]/text()").extract()
company = each.xpath("p[3]/text()").extract()
#xpath返回的是包含一个元素的列表
item['job_name'] = job_name[0]
item['salary'] = salary[0]
item['company'] = company[0]
items.append(item)
# yield数据给pipeline进行处理
yield item
(4) pipelines处理数据
当数据由spider采集完成时候,封装在Item对象中通过yield数据交给pipelines进行处理,在pipelines中按照定义的顺序执行Item对象的处理,每个Pipelines都是python中的类型,可以执行后续的数据筛选、验证、存储等操作
在实际开发过程中,参考官方文档,Item类型中默认定义如下方法:
- open_spider(self, spider):当爬虫程序启动的时候调用
- process_item(self, item, spider):当爬虫处理完数据交给pipelines处理时调用,必须实现该方法
- close_spider(self, spider):当爬虫程序关闭时调用
如下:
# coding:utf-8
class SomePipeline():
def __init__(self):
# 可选:主要进行程序中数据初始化操作使用
def open_spider(self, spider):
# 可选,当爬虫启动时调用
def process_item(self, item, spider):
# 必须,当爬虫程序yield item数据时调用
def close_spider(self, spider):
# 可选,当爬虫程序关闭时调用
处理完成之后,需要修改爬虫程序设置文件settings.py中的PIPELINES配置项启用Pipeline,同时通过一个0~1000之间的整数来定义执行的优先级[值越小优先级越高]
ITEM_PIPELINES = {
'myspider.pipelines.SomePipeline': 200
}
重新开发我们的招聘爬虫程序的pipelines处理模块
# coding:utf-8
class ZhaopinPipeline(object):
def process_item(self, item, spider):
# 这里可以执行item中数据的验证、存储等工作
print(item)
return item
那么,请思考,如何在pipelines中,将采集到的数据存储到数据库中进行记录呢?
_编辑:大牧莫邪,未完待续,下一节更精彩~智联招聘数据采集