安装
pip install scrapy -i https://pypi.tuna.tsinghua.edu.cn/simple
概述
scrapy是python下的一个爬虫(spider)库,据说也是目前使用最广的爬虫库
scrapy包含两种运作方式,一种是在命令行中执行(就像在命令行直接直接python一样),一种是建立项目通过运行项目运行(就像运行python文件一样)
教程
网上教程很多,官方的resources里面也有很多链接到其他网站的教程,但是看来看去还是官方的教程讲的最明确,不仅会讲怎么用,还会讲为什么,这里记录一下官网初级教程里面的一点东西和使用注意事项。
运行方式
命令行
安装完成后,可直接在命令行下执行scrapy shell
其中scrapy的命令行模式,外观长得和ipython一模一样。下面以一个网页为例说说怎么用:
- fetch 会爬去一个网页,不需要接他的返回值,fetch之后会自动有一个response变量里面存了网页的内容
fetch("http://openaccess.thecvf.com/CVPR2019.py")
- .css函数用于通过CSS过滤从response提取特定的内容,这里的过滤可以通过CSS中的id、class、标签等,id用'#'开头,class用'.'开头,如果是标签的话,就直接写。这个例子指的是所有class是ptitle的标签中的超链接。"::text"的含义是返回提取出的标签中的文本内容(即写在一对之间的内容)。如果去掉::text,则会返回包含标签在内的所有内容。如果把::text替换为::attr(href)则代表返回标签中名字为href的属性,在这个例子中,就是返回超链接的地址。
response.css(".ptitle a::text").extract()
脚本程序
scrapy startproject some_name # 在当前目录生成一个名字为some_name的项目
cd some_name
scrapy genspider cvpr2019 openaccess.thecvf.com/CVPR2019.py # 在当前项目中生成一个名字为cvpr2019的爬虫,注意这个
项目目录如下,其中生成的所有爬虫都会在spiders目录下,所以现在spiders目录下应该还有一个cvpr2019.py的文件(下面没写出来):
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。name是当前爬虫的名字,同一个项目中的任意两个爬虫都不能同名。start_requests
必须返回一组可迭代的Request,爬虫会首先按照这个list逐个爬去,是爬虫开始的地方。allowed_domains
是允许访问的域,所有不以这个域开头的网址在爬去过程中都会被过滤掉。
class Cvpr2019Spider(scrapy.Spider):
name = "cvpr2019"
allowed_domains = ['openaccess.thecvf.com']
def start_requests(self):
urls = [
'http://quotes.toscrape.com/page/1/',
'http://quotes.toscrape.com/page/2/',
]
for url in urls:
yield scrapy.Request(url=url, callback=self.parse)
为了简便起见,start_requests
不写,而是定义一个start_urls
的变量,默认的start_requests会读这个变量然后生成对应的一组requests。请求得到的response的默认回调函数是self.parse
,当然如果是手写start_requests
的话,就可以自定义回调函数的名字了。所以,上述代码等价于下列代码:
class Cvpr2019Spider(scrapy.Spider):
name = "cvpr2019"
allowed_domains = ['openaccess.thecvf.com']
start_urls = ['http://quotes.toscrape.com/page/1/', 'http://quotes.toscrape.com/page/2/']
每个接受返回的函数都有一个变量response作为返回,response的内容就和命令行执行fetch之后得到的response一样一样的。如下:
class QuotesSpider(scrapy.Spider):
name = "quotes"
start_urls = [
'http://quotes.toscrape.com/page/1/',
'http://quotes.toscrape.com/page/2/',
]
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(),
}
parse中yield的内容如果是字典等等,就会直接输出,如果是Request,则这个request会被加入到urls的队列中,因此就可以递归的去爬去网页了,如下面的代码。注意新的request的解析函数也可以不是parse,而定义其他的函数解析新的请求返回。下面的代码中用了urljoin
,它的作用是把新的url加载当前url的后面,因为出现在网页中的url经常是相对路径,urljoin
就是把当前的网页的路径和相对路径拼接起来形成绝对路径。
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)
上面的yield新的请求还有一种简便写法:yield response.follow(next_page, callback=self.parse)
,这里用了follow
函数,它会返回一个请求,并且会自动把next_page当做相对路径,免去了自己写urljoin
的麻烦。
注意:请求的url必须是协议开头的(如http://),这个东西不能忽略,例如直接写www.xxx.com不行,而必须是http://www.xx.com。否则会报Missing scheme in request URL
的错误。另allowed_domains的末尾不能包含多余的/,否则会有错误,可能把不想过滤的网址也过滤掉。
写完之后,在项目目录下敲scrapy crawl xx -o output.out -a save_file 123.md
即可开始爬虫,其中xx爬虫的明细,即class中的name。后面的两个参数都是可选的,-o
表示把输出内容dump到output.out
文件中,这里的输出用print是没用的,它特指解析每个请求后parse函数yield的内容。-a
是传入的参数,它会被直接传递到爬虫的类的内部成为成员变量,所以,我们可以在函数中直接用self.save_file
访问这个变量。
下面给一个完整的例子,这个例子从cvpr2019的官网爬去了所有论文的题目和摘要并写到文件里,另每篇论文在网页上都有PDF的地址,所以只要稍加修改就能下载全部全文。
# -*- coding: utf-8 -*-
import scrapy
class Cvpr2019Spider(scrapy.Spider):
name = 'cvpr2019'
allowed_domains = ['openaccess.thecvf.com']
start_urls = ['http://openaccess.thecvf.com/CVPR2019.py/']
def parse(self, response):
for url in response.css(".ptitle a::attr(href)").extract():
url = "http://openaccess.thecvf.com/" + url
yield scrapy.Request(url, callback=self.parse_single_paper)
def parse_single_paper(self, response):
papertitle = response.css("#papertitle::text").extract_first().lstrip("\n")
abstract = response.css("#abstract::text").extract_first().lstrip("\n").rstrip("\r")
save_file = getattr(self, "save_file", None)
if save_file is None:
assert False, "save_file should not be None"
with open(save_file, "a") as file:
file.write("####%s\n" % papertitle)
file.write(abstract + "\n\n\n")