Scrapy初步

安装

pip install scrapy -i https://pypi.tuna.tsinghua.edu.cn/simple

概述

scrapy是python下的一个爬虫(spider)库,据说也是目前使用最广的爬虫库

scrapy包含两种运作方式,一种是在命令行中执行(就像在命令行直接直接python一样),一种是建立项目通过运行项目运行(就像运行python文件一样)

教程

网上教程很多,官方的resources里面也有很多链接到其他网站的教程,但是看来看去还是官方的教程讲的最明确,不仅会讲怎么用,还会讲为什么,这里记录一下官网初级教程里面的一点东西和使用注意事项。

运行方式

命令行

安装完成后,可直接在命令行下执行scrapy shell其中scrapy的命令行模式,外观长得和ipython一模一样。下面以一个网页为例说说怎么用:

  1. fetch 会爬去一个网页,不需要接他的返回值,fetch之后会自动有一个response变量里面存了网页的内容
fetch("http://openaccess.thecvf.com/CVPR2019.py")
  1. .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")


你可能感兴趣的:(Scrapy初步)