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

其他工具

Scrapy Shell

1
scrapy shell http://xxxxxxxxx

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

1
2
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
<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
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
response.css('a[href*=image]::attr(href)').getall() 

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

补充:

1
2
id选择器: '#', 如#tag 代表id为tag的元素
类选择器: '.', 如span.text代表class值为text的span

选择器的其他用法

选择器列表

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

1
[<Selector xpath='//title/text()' data='Example website'>]

返回的是一个列表,因此需要使用get()getall()来提取文本

当未匹配时返回None,可设置默认返回值: xxx.get(default='not-found')

选择器+正则

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

1
response.css('a::text').re(r'\d')

选择器连接

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

1
2
3
4
5
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
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
<ul class="list">
<li>1</li>
<li>2</li>
<li>3</li>
</ul>
<ul class="list">
<li>4</li>
<li>5</li>
<li>6</li>
</ul>

'//li[1]' : ['<li>1</li>', '<li>4</li>']
'(//li)[1]' : ['<li>1</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.cn/post/c4740d31.html
作者
小周
发布于
2022年12月16日
更新于
2023年2月27日
许可协议