Scrapy2.7速通

注意:据我观察,scrapy很多版本的文档都不大一样。在一些早期版本,有好心人将英文文档翻译成中文(大概率是机翻或者AI翻的),而在最新版本中内容又不一样。因此建议参考的路径是 pip官网scrapy主页查看最新版本,并找到相关最新的文档。(不是说旧版文档不能用,每个文档都有差别容易,让人摸不着头脑)

scrapy简单调用过程

爬虫通过向start_urls发送请求,调用默认的回调函数,并将相应对象作为参数传给它
在回调函数中,使用css选择器遍历元素,将寻找到的结果生成一个字典通过生成器返回,并找到下一个链接继续使用回调函数。
scrapy的请求调度是异步的,scrapy无需等待上一个请求被处理完,可以同时发送另一个请求继续处理

Scrapy纯代码创建爬虫

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
37
38
39
40
41
42
43
44
45
46
47
48
49
50
class
QuotesSpider
(scrapy.Spider):
# 自定义爬虫需要继承scrapy类
# 配置爬虫名字和初始URL,注意url是一个列表
name =
'quotes'
start_urls = [
'http://quotes.toscrape.com/tag/humor/'
,]
# 解析入口
def
parse
(
self
, response
):
# 抓取所有class属性为quote的<div>,该<div>应该包含所需的text和auther
for
quote
in
response.css(
'div.quote'
):
yield
{
# 通过生成器返回
# 获取class属性为text的<span>的text内容,即句子内容
'text'
: quote.css(
'span.text::text'
).get(),
# 通过xpath方式获取 <span>下的<small>标签的文本内容,即作者
'author'
: quote.xpath(
'span/small/text()'
).get(),
}
# 获取下一页所在的链接
next_page = response.css(
'li.next a::attr("href")'
).get()
if
next_page is
not
None:
yield
response.follow(next_page,
self
.parse)

然后在终端使用下列命令运行该文件,如需设置字符集在后面加上-s FEED_EXPORT_ENCODING='utf-8'

1
2
3
4
5
scrapy runspider spider1
.py
-o
quotes
.json

问题1:这是一个.py文件,应该可以通过纯代码的方式来运行,但目前位置没有看到相关的教程。
问题2:为什么要用yield而不用return:因为需要重复调用,return会直接退出函数。

通过Scrapy模板快速创建爬虫

创建爬虫项目

注意,在创建项目时,建议不要以某个单功能/网站爬虫来命名。一般以范围比较大的,例如电影爬虫、新闻爬虫等为名。因为一个项目下面允许创建多个爬虫,单个网站的爬虫就放在项目里面,通过模板命令来创建即可

1
scrapy startproject stockstar

通过以上命令,将会创建一个名为stockstar的目录,目录结构如下:

按模板初始化爬虫

1
2
3
4
5
6
7
8
9
cd stockstar 
# 进入创建好的爬虫项目目录
scrapy genspider stock
quote
.stockstart.com
scrapy genspider :初始化爬虫命令
stock : 爬虫名
quote
.stockstart.com : 根域名

创建爬虫初始URL和parse函数,在spider目录下创建一个stock.py文件,自动生成start_url和parse函数,爬虫的逻辑就在parse中编写。

运行爬虫

在根目录新建main.py,执行代码运行爬虫

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
from scrapy
.cmdline
import execute
execute
([
"scrapy"
,
"crawl"
,
"stock"
,
"-o"
,
"items.json"
])
或:
cmdline
.execute
(
"scrapy crawl stock"
.split
())

这行代码等价于终端执行scrapy crawl myspider -o items.json
如果需要生成其他格式,则执行scrapy crawl myspider -o item.csv/item.xml,如需导出时指定编码格式,则在后面添加-s FEED_EXPORT_ENCODING=utf-8

settings.py配置

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
ROBOTSTXT_OBEY
=
False
# 是否遵守协议,项目创建完默认为Ture,建议为False
CONCURRENT_REQUESTS
=
# 最大并发量
DOWNLOAD_DELAY
=
# 下载延迟 3 秒
DEFAULT_REQUEST_HEADERS
=
{...}
# 请求报头
SPIDER_MIDDLEWARES
=
{...}
# 爬虫中间件
DOWNLOADER_MIDDLEWARES
=
{...}
# 下载中间件
ITEM_PIPELINES
=
{...}
# 管道

其他工具

Scrapy Shell

1
2
3
scrapy shell http:
//
xxxxxxxxx

创建Scrapy Shell终端,Scrapy将对网页进行访问后返回response,在scrapy shell中可以随意使用选择器进行测试、选取 ,而不需要多次访问该网页,相当于将该网页的内容下载到本地供你测试使用。

1
2
3
4
5
6
7
8
9
10
response.css(
''
).
get
()
response.xpath(
''
).
get
()

scrapy view

通过scrapy view http://xxxxxx.com可以将spider可以爬取到的页面保存到本地或者浏览器打开,在保存到本地后,可以对其进行分析,无需手动对目标页面发请求

scrapy list

根目录 执行scrapy list,可以查看当前项目中所有spider

scrapy settings

格式:scrapy setting --get BOT_NAME获得设定值。

选择器的使用

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
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
<
html
>
<
head
>
<
base
href
=
'http://example.com/'
/>
<
title
>
Example website
</
title
>
</
head
>
<
body
>
<
div
id
=
'images'
>
<
a
href
=
'image1.html'
>
Name: My image 1
<
br
/>
<
img
src
=
'image1_thumb.jpg'
/>
</
a
>
<
a
href
=
'image2.html'
>
Name: My image 2
<
br
/>
<
img
src
=
'image2_thumb.jpg'
/>
</
a
>
<
a
href
=
'image3.html'
>
Name: My image 3
<
br
/>
<
img
src
=
'image3_thumb.jpg'
/>
</
a
>
<
a
href
=
'image4.html'
>
Name: My image 4
<
br
/>
<
img
src
=
'image4_thumb.jpg'
/>
</
a
>
<
a
href
=
'image5.html'
>
Name: My image 5
<
br
/>
<
img
src
=
'image5_thumb.jpg'
/>
</
a
>
</
div
>
</
body
>
</
html
>
<
div
class
=
"hero shout"
>
<
time
datetime
=
"2014-07-23 19:00"
>
Special date
</
time
>
</
div
>

针对上方的HTML结构,不同的选择器有不同的写法。

Xpath选择器

基本用法

格式: response.selector.xpath('//span/text()').get()
获取文本:/text()
获取属性:/@href
过滤标签:

1
2
3
4
5
response.xpath('//a[contains(
@href,
"image"
)]/
@href').getall()

即:选择href属性的值包含image<a>,输出其href

CSS选择器

基本用法

格式:response.selector.css(‘span::text’).get()
获取文本:::text *::text , 后者用于选择某节点下所有文本
获取属性:::attr("href") 或者 .attrib['href'] , 后者一般用于在唯一元素中使用,因此无需在用get()
过滤标签:

1
2
3
4
5
6
7
response
.css
(
'a[href*=image]::attr(href)'
)
.getall
()

即:选择href属性的值包含image<a>,输出其href
补充:

1
2
3
4
5
6
7
8
9
id选择器: 
'#'
, 如
#tag 代表id为tag的元素
类选择器:
'.'
, 如span.text代表
class
值为text的span

选择器的其他用法

选择器列表

选择器为所选取的标签构建一个选择器列表SelectorList:

1
2
3
4
5
6
7
8
9
[
<Selector
xpath=
'//title/text
()' data=
'Example
website
'>
]

返回的是一个列表,因此需要使用get()getall()来提取文本
当未匹配时返回None,可设置默认返回值: xxx.get(default='not-found')

选择器+正则

xpath和css选择器都支持使用正则表达式提取方法,selector.re()将返回unicode字符串列表

1
2
3
4
5
response.css(
'a::text'
).re(
r'\d'
)

选择器连接

单纯的某种选择器可能不太方便使用,scrapy选择器允许在合适的地方联合使用css和xpath

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
from
scrapy import Selector
sel = Selector(
text
='<
div
class
=
"hero shout"
><
time
datetime=
"2014-07-23 19:00"
>Special
date
</
time
></
div
>')
sel.css('.shout').xpath(./
time
/@datatime).getall()

嵌套选择器

两种选择器都返回的列表是相同的,既然是列表就可以用于迭代,在迭代过程中,每个元素都可以再次使用选择器来匹配元素

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
links = response.xpath(
'//a[contains(@href, "image")]'
)
for
index, link
in
enumerate
(links):
args = (index, link.xpath(
'@href'
).get(), link.xpath(
'img/@src'
).get())
print
(
'Link number %d points to url %r and image %r'
% args)

如果在内部使用的选择器是xpath,需要注意路径问题:
如果使用相对路径,则需要这样用 ‘.//xx’ 或者 直接选择所需元素,因为外部的选择器已经是根路径

带括号的条件

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
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
<ul 
class
="
list
">
<
li
>
</
li
>
<
li
>
</
li
>
<
li
>
</
li
>
</
ul
>
<
ul
class
="
list
">
<
li
>
</
li
>
<
li
>
</
li
>
<
li
>
</
li
>
</
ul
>
'//
li
[
]' : ['<
li
>
</
li
>', '<
li
>
</
li
>']
'(//
li
)[
]' : ['<
li
>
</
li
>']
  • 带着括号的(node)相当于获取所有符合元素的列表的第[n]个
  • 不带括号时对整个response进行筛选,只要符合就取出

例如上面的例子:
'//li[1]'可以匹配到2个<ul>下的<li>, 因为他们都表示li的第一个
'(//li)[1]'则匹配整个文档中li[1],因为加了括号后整个文档的li是一个整体,只取这个整体里的第一个。
除了自带的选择器,Python还有其他库也可以进行解析:

  1. beautifulSoup,根据HTML构造Python对象,缺点是速度慢
  2. lxml,可解析xml和HTML,非python标准库

选择器语法大全

CSS选择器

表达式 说明
  • | 所有节点

#ElementId | ID为ElementId的节点
.clsName | class为clsName的节点
li a | li元素下所有a元素
ul + p | ul下第一个P元素
div#eId > ul | id为eId的元素下所有ul元素
div.clsName | clsss为clsName的div元素
ul ~ p | 与ul相邻的所有p元素
a[title] | 所有有tittle属性的a元素
a[href=“http://www.baidu.com”] | 所有href为指定值的a元素
a[href*=“job”] | 所有href属性包含指定值的a元素
a[href^=“http”] | 所有href属性值以指定值开头的a元素
a[href$=“.jpg”] | 素有href属性值以指定值结尾的a元素
input[type=radio]:checked | 状态为选中的radio元素
div:not(#eleId) | 所有id不是指定值的div元素
li:nth-child(3) | 第三个li元素
tr:nth-child(2n) | 第偶数个 tr 元素
以下选择器未尝试过 |
div,p | 选择所有 元素和所有 元素
div>p | 选择父元素为 元素的所有 元素
[title~=flower] | 选择 title 属性包含单词 “flower” 的所有元素
p:first-child | 选择属于父元素的第一个子元素的每个 元素

Xpath语法大全

表达式 说明
div 选取所有div元素的所有子元素
/div 选取根元素div
div/a 素有属于div的子元素的a元素
//div 所有div元素
div//span 所有属于div元素的后代span元素,无论在那个层级
//@class 选取所有名为class的属性
/div/p[1] 属于div子元素的第一个p元素
/div/p[last()] 属于div子元素的最后一个p元素
/div/p[last()-1] 属于div子元素的倒数第二个p元素
/div[@lang] 所有有lang属性的div元素
//div[@lang=‘eng’] 所有lang属性为eng的div元素
.//header//span[contains(@class,‘main-title-rating’)]/@title .符号表示当前路径,一般在循环中使用;contains中表示class中包含指定值元素,@title是取元素的title属性
  • | 匹配任意元素
    @* | 匹配任意属性的元素
    node() | 匹配任意类型的元素

Scrapy2.7速通
https://zhouyinglin.top/2022/12/16/Scrapy2.7速通/
作者
小周
发布于
2022年12月16日
许可协议