Python Scrapy 库的使用

Scrapy 不是一个函数功能库,而是一个爬虫框架

官网:https://scrapy.org/

中文文档:https://scrapy-chs.readthedocs.io/zh_CN/0.24/index.html

安装

1
pip3 install scrapy

命令行测试

1
scrapy ‐h

框架结构

1、Engine 从 Spider 处获得爬取请求 (Request)

2、Engine 将爬取请求转发给 Scheduler,用于调度

3、Engine 从 Scheduler 处获得下一个要爬取的请求

4、Engine 将爬取请求通过中间件发送给 Downloader

5、爬取网页后,Downloader 形成响应(Response),通过中间件发给 Engine

6、Engine 将收到的响应通过中间件发送给 Spider 处理

7、Spider 处理响应后产生爬取项(scraped Item)和新的爬取请求(Requests)给Engine

数据流的出入口

Engine 控制各模块数据流,不间断从 Scheduler 处获得爬取请求,直至请求为空

框架入口:Spider 的初始爬取请求
框架出口:Item Pipeline

框架解析

Engine
  • 控制所有模块之间的数据流
  • 根据条件触发事件
  • 不需要用户修改
Downloader
  • 根据请求下载网页
  • 不需要用户修改
Downloader Middleware
  • 目的:实施 Engine、Scheduler 和 Downloader 之间进行用户可配置的控制
  • 功能:修改、丢弃、新增请求或响应
  • 用户可以编写配置代码
Scheduler
  • 对所有爬取请求进行调度管理
  • 不需要用户修改
Spider
  • 解析 Downloader 返回的响应(Response)
  • 产生爬取项(scraped item)
  • 产生额外的爬取请求(Request)
  • 需要用户编写配置代码
Spider Middleware
  • 目的:对请求和爬取项的再处理
  • 功能:修改、丢弃、新增请求或爬取项
  • 用户可以编写配置代码
Item Pipelines
  • 以流水线方式处理 Spider 产生的爬取项
  • 由一组操作顺序组成,类似流水线,每个操作是一个Item Pipeline 类型
  • 可能操作包括:清理、检验和查重爬取项中的 HTML 数据、将数据存储到数据库
  • 需要用户编写配置代码

requests vs Scrapy

requests scrapy
页面级爬虫 网站级爬虫
功能库 框架
并发性考虑不足,性能较差 并发性好,性能较高
重点在于页面下载 重点在于爬虫结构
定制灵活 一般定制灵活,深度定制困难
上手十分简单 入门稍难

Scrapy 爬虫的常用命令

Scrapy 是为持续运行设计的专业爬虫框架,提供操作的是 Scrapy 命令行

1
scrapy <command> [options] [args]
命令 说明 格式
startproject 创建一个新工程 scrapy startproject “name” [dir]
genspider 创建一个爬虫 scrapy genspider [options] “name” “domain”
settings 获得爬虫配置信息 scrapy settings [options]
crawl 运行一个爬虫 scrapy crawl “spider”
list 列出工程中所有爬虫 scrapy list
shell 启动URL调试命令行 scrapy shell [url]

Scrapy 爬虫简单实例

演示的 HTML 页面地址:https://python123.io/ws/demo.html

步骤1:新建一个 Scrapy 爬虫工程

选择一个目录,执行如下命令

1
scrapy startproject scrapy_demo

生成的工程目录

1
2
3
4
5
6
7
8
9
10
11
12
scrapy_demo                 -->     外层目录
scrapy.cfg --> 部署 Scrapy 爬虫的配置文件
scrapy_demo --> Scrapy 框架的用户自定义 Python 代码
__init__.py --> 初始化脚本
__pycache__ --> 缓存目录,无需修改
items.py --> Items 代码模板(继承类)
middlewares.py --> Middlewares 代码模板(继承类)
pipelines.py --> Pipelines 代码模板(继承类)
settings.py --> Scrapy 爬虫的配置文件
spiders --> Spiders 代码模板目录(继承类)
__init__.py --> 初始文件,无需修改
__pycache__ --> 缓存目录,无需修改

步骤2:在工程中产生一个 Scrapy 爬虫

进入工程目录(scrapy_demo),执行如下命令

1
scrapy genspider demo python123.io

该命令作用:

  • 生成一个名称为 demo 的 spider
  • 在 spiders 目录下增加代码文件 demo.py
  • 该命令仅用于生成 demo.py,该文件也可以手工生成

生成的 demo.py 文件

1
2
3
4
5
6
7
8
9
10
11
# -*- coding: utf-8 -*-
import scrapy

class DemoSpider(scrapy.Spider):
name = 'demo'
allowed_domains = ['python123.io']
start_urls = ['http://python123.io/']

# parse()用于处理响应,解析内容形成字典,发现新的URL爬取请求
def parse(self, response):
pass

步骤3:配置产生的 spider 爬虫

  • 初始 URL 地址
  • 获取页面后的解析方式
1
2
3
4
5
6
7
8
9
10
11
12
13
# -*- coding: utf-8 -*-
import scrapy

class DemoSpider(scrapy.Spider):
name = 'demo'
# allowed_domains = ['python123.io'] # 可选
start_urls = ['http://python123.io/ws/demo.html']

def parse(self, response):
fname = response.url.split('/')[-1]
with open(fname, 'wb') as f:
f.write(response.body)
self.log('Saved file %s.' % fname)

步骤4:运行爬虫,获取网页

执行如下命令

1
scrapy crawl demo

在工程目录(scrapy_demo)下,就可以看到 demo.html 文件

demo.py 文件代码的完整版本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# -*- coding: utf-8 -*-
import scrapy

class DemoSpider(scrapy.Spider):
name = 'demo'

def start_requests(self):
urls = ['http://python123.io/ws/demo.html']
for url in urls:
yield scrapy.Request(url=url, callback=self.parse)

def parse(self, response):
fname = response.url.split('/')[-1]
with open(fname, 'wb') as f:
f.write(response.body)
self.log('Saved file %s.' % fname)

yield 关键字

包含 yield 语句的函数是一个生成器

生成器每次产生一个值(yield 语句),函数被冻结,被唤醒后再产生一个值

生成器是一个不断产生值的函数

1
2
3
4
5
6
7
8
def gen(n):
for i in range(n):
yield i**2


for i in gen(5):
print(i, "", end="")
# 0 1 4 9 16

生成器的优势

  • 更节省存储空间
  • 响应更迅速
  • 使用更灵活

Scrapy 爬虫的数据类型

Request (scrapy.http.Request)

Request 对象表示一个 HTTP 请求由 Spider 生成,由 Downloader 执行

属性或方法 说明
.url Request 对应的请求 URL 地址
.method 对应的请求方法,’GET’ ‘POST’等
.headers 字典类型风格的请求头
.body 请求内容主体,字符串类型
.meta 用户添加的扩展信息,在 Scrapy 内部模块间传递信息使用
.copy() 复制该请求
Response (scrapy.http.Response)

Response 对象表示一个HTTP响应,由 Downloader 生成,由 Spider 处理

属性或方法 说明
.url Response 对应的 URL 地址
.status HTTP 状态码,默认是 200
.headers Response 对应的头部信息
.body Response 对应的内容信息,字符串类型
.flags 一组标记
.request 产生 Response 类型对应的 Request 对象
.copy() 复制该响应
Item (scrapy.item.Item)

Item 对象表示一个从 HTML 页面中提取的信息内容,由 Spider 生成,由 Item Pipeline 处理
Item 类似字典类型,可以按照字典类型操作

Scrapy 爬虫提取信息的方法

Scrapy 爬虫支持多种 HTML 信息提取方法:

  • Beautiful Soup
  • lxml
  • re
  • XPath Selector
  • CSS Selector

CSS Selector 的基本使用

1
<HTML>.css('a::attr(href)').extract()

股票数据 Scrapy 爬虫实例

获取股票列表:
​ 东方财富网:http://quote.eastmoney.com/stocklist.html
获取个股信息:
​ 百度股票:https://gupiao.baidu.com/stock/
​ 单个股票:https://gupiao.baidu.com/stock/sz002439.html

编写 spider 处理链接爬取和页面解析,编写 pipelines 处理信息存储

步骤1:建立工程和 Spider 模板

依次执行如下命令

1
2
3
scrapy startproject BaiduStocks
cd BaiduStocks
scrapy genspider stocks baidu.com

步骤2:编写 Spider

配置 stocks.py 文件
修改对返回页面的处理
修改对新增 URL 爬取请求的处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
# -*- coding: utf-8 -*-
import scrapy
import re


class StocksSpider(scrapy.Spider):
name = "stocks"
start_urls = ['http://quote.eastmoney.com/stocklist.html']

def parse(self, response):
for href in response.css('a::attr(href)').extract():
try:
stock = re.findall(r"[s][hz]\d{6}", href)[0]
url = 'https://gupiao.baidu.com/stock/' + stock + '.html'
yield scrapy.Request(url, callback=self.parse_stock)
except:
continue

def parse_stock(self, response):
infoDict = {}
stockInfo = response.css('.stock-bets')
name = stockInfo.css('.bets-name').extract()[0]
keyList = stockInfo.css('dt').extract()
valueList = stockInfo.css('dd').extract()
for i in range(len(keyList)):
key = re.findall(r'>.*</dt>', keyList[i])[0][1:-5]
try:
val = re.findall(r'\d+\.?.*</dd>', valueList[i])[0][0:-5]
except:
val = '--'
infoDict[key]=val

infoDict.update(
{'股票名称': re.findall('\s.*\(',name)[0].split()[0] + \
re.findall('\>.*\<', name)[0][1:-1]})
yield infoDict

步骤3:编写 ITEM Pipelines

配置 pipelines.py 文件
定义对爬取项(Scraped Item)的处理类
配置 ITEM_PIPELINES 选项

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# -*- coding: utf-8 -*-

# Define your item pipelines here
#
# Don't forget to add your pipeline to the ITEM_PIPELINES setting
# See: http://doc.scrapy.org/en/latest/topics/item-pipeline.html


class BaidustocksPipeline(object):
def process_item(self, item, spider):
return item

class BaidustocksInfoPipeline(object):
def open_spider(self, spider):
self.f = open('BaiduStockInfo.txt', 'w')

def close_spider(self, spider):
self.f.close()

def process_item(self, item, spider):
try:
line = str(dict(item)) + '\n'
self.f.write(line)
except:
pass
return item

配置 ITEM_PIPELINES 选项

配置 settings.py 文件

1
2
3
4
5
# Configure item pipelines
# See http://scrapy.readthedocs.org/en/latest/topics/item-pipeline.html
ITEM_PIPELINES = {
'BaiduStocks.pipelines.BaidustocksInfoPipeline': 300,
}

可选配置并发连接选项

settings.py 文件中

选项 说明
CONCURRENT_REQUESTS Downloader 最大并发请求下载数量,默认 32
CONCURRENT_ITEMS Item Pipeline 最大并发ITEM处理数量,默认100
CONCURRENT_REQUESTS_PER_DOMAIN 每个目标域名最大的并发请求数量,默认 8
CONCURRENT_REQUESTS_PER_IP 每个目标 IP 最大的并发请求数量,默认 0,非 0 有效

执行程序

1
scrapy crawl stocks