站点数据收集-Scrapy使用笔记

前言

网站数据收集方法有很多,比如最基础的requests,简单几行就可以获取网页信息。使用selenium模拟网页点击可以绕过很多反爬策略,编写思路也不同于其他的方法。用scrapy框架来做的话可以清楚地进行目标拆分,并利用内置的线程池可以非常高效地获取信息。

本文以scrapy为目标,总结基础的使用方法,以供后续复习。

配置

本地配置好python及pip后,使用 pip install scrapy既可以安装scrapy。

基本使用

新建工程

scrapy在使用时,需要在主机命令行里 scrapy startproject <projectname></projectname>创建一个项目,比如运行 scrapy startproject example后生成example文件夹,内容如图所示。

站点数据收集-Scrapy使用笔记

; 添加目标网站

命令行也会提示进入example目录,并运行scrapy genspider来创建一个spider。比如运行 scrapy genspider example_spider example.com,之后会在spiders文件夹下生成一个example_spider.py文件。爬虫的代码就需要写在这个文件内。

GET请求
import scrapy

class ExampleSpiderSpider(scrapy.Spider):
    name = 'example_spider'
    allowed_domains = ['example.com']
    start_urls = ['http://example.com/']

    def parse(self, response):
        pass
POST请求
import scrapy

class ExampleSpiderSpider(scrapy.Spider):
    name = 'example_spider'
    allowed_domains = ['example.com']
    urls = [
            'https://example.com/page/1/',
            'https://example.com/page/2/',
        ]

    def start_requests(self):
        for target in urls:

           yield scrapy.FormRequest(
                url=url,
                formdata={'arg1':'xxx','arg2':'xxx'},
                callback=self.parse,
                meta={'arg1':1,'arg2':2}
            )

           yield scrapy.Request(
                url=url,
                method='POST',
                body = json.dumps({'arg1':'xxx','arg2':'xxx'}),
                headers = {'Content-Type':'application/json'},
                callback=self.parse,
                meta={'arg1':1,'arg2':2}
            )

    def parse(self, response):
        pass

需要注意的是:

  1. name 是爬虫名字,即spidername,之后运行需要指定这个名字。
  2. allowed_domains 指定允许爬取的域名,也可以不要。
  3. start_urls指定需要爬取哪些网站,运行时会一个一个向这些网站发请求,并将响应传给parse函数。如果需要动态生成目标网站,可以删掉这个 start_urls变量,并添加一个 start_requests(self)成员函数(需要使用 yield scrapy.Request(url = <targetwebsite>, callback=self.parse)</targetwebsite>作为返回值。爬虫运行时如果发现没有定义 start_urls变量,则会调用这个函数。
  4. scrapy.Request用于发送GET请求。可以添加一个 cb_kwargs参数,它接受一个字典,并可以在 parse(self, response, **kwargs)中通过 kwargs来获取这个字典, 以实现自定义的参数传递
  5. scrapy.FormRequest用于发送POST请求,请求体放在 formdata中,参数应当都是字符串类型。

这里对官方文档作以修改举例:

import scrapy

class QuotesSpider(scrapy.Spider):
    name = "quotes"

    def start_requests(self):
        urls = [
            'https://quotes.toscrape.com/page/1/',
            'https://quotes.toscrape.com/page/2/',
        ]
        for url in urls:
            yield scrapy.Request(url=url, callback=self.parse, cb_kwargs={'this_url':url})

    def parse(self, response, **kwargs):
        page = response.url.split("/")[-2]
        url = kwargs['this_url']
        filename = f'quotes-{page}.html'
        with open(filename, 'wb') as f:
            f.write(response.body)
        self.log(f'Saved file {filename}')

启动爬取

在最外层的example目录下运行 scrapy crawl <spidername></spidername>,即可开始爬取。

再以官网文档为例:

import scrapy

class QuotesSpider(scrapy.Spider):
    name = "quotes"
    start_urls = [
        'https://quotes.toscrape.com/page/1/',
        'https://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(),
            }

response.css来提取元素,其含义不言自明。也可以通过response.text获取文本信息。

除了get外,也可以使用extract_first、re_first等函数,详见这里

这里的parse函数yield了一个字典,可以在运行时指定保存文件: scrapy crawl <spidername> -O <output.jl></output.jl></spidername>来将其保存到文件中,方便后续处理。jl是jsonline即单行json,可以在python中使用简单的文件逐行遍历配合json来处理。其中 -O表示覆盖输出文件, -o表示在输出文件后追加。可以添加 -L ERROR来忽略运行时无关紧要的输出。

对公开API继续爬取时,jl有奇效。

错误处理

可以在 scrapy.Requestscrapy.FormRequest函数中添加一个errback参数,来指定一个自定义的error_handling函数,用法类似于callback。

如下所示:

import scrapy

class QuotesSpider(scrapy.Spider):
    name = "quotes"

    def start_requests(self):
        urls = [
            'https://quotes.toscrape.com/page/1/',
            'https://quotes.toscrape.com/page/2/',
        ]
        for url in urls:
            yield scrapy.Request(url=url, callback=self.parse, cb_kwargs={'this_url':url}, errback=self.myerr)

    def myerr(self, failure):
        print(repr(failure))
        print(failure.request.url)
        print(failure.request.cb_kwargs['this_url'])

    def parse(self, response, **kwargs):
        url = kwargs['this_url']

进阶使用

缓存(重要)

可以将项目下的 settings.py里最下面的HTTP_CACHE_XXX启用,来自动将爬下来的数据缓存在本地。当发现xpath写得不对时可以快速更新结果而不用重新请求网站。使用默认的 HTTPCACHE_STORAGE = 'scrapy.extensions.httpcache.FilesystemCacheStorage'时,默认会保存在项目的 .scrapy/<crawler name></crawler>下。之前大致读了下缓存的代码,大致逻辑是根据url、method和body做一个sha1运算,把hexdigest作为请求的hash,哈希前两个十六进制数作为一级目录,完整的hash作为二级目录,每个二级目录下存放meta、pickled_meta、request_body、request_headers、response_body和response_headers等数据。

代理(重要)

有时候爬快了网站会临时ban掉ip,而设置爬取速度不利于大规模爬取(而且有的网站限制会更严格),可以使用代理转发请求。爬国内的网站的话,我自己在用的是快代理的隧道代理,只需要设置下请求的proxy就可以自动切换ip,比较省心。用起来比较稳定(就是有点小贵233),如果有便宜稳定的国内代理欢迎评论!

(没试过它的免费代理 不知道可用性如何)

import base64
username = 'xxxxx'
passwd = 'xxxxx'
proxy_ip = 'xxxx.kdltps.com'
proxy_port = '15818'

meta = {'proxy': f'http://{proxy_ip}:{proxy_port}'}
code = base64.b64encode(f'{username}:{passwd}'.encode()).decode()

headers = {
    "Proxy-Authorization": f"Basic {code}",
}
def start_requests(self):
    yield scrapy.Request(
        headers = headers,
        meta = meta,
        )

一些注意事项

有时候可能出现url在预设列表里,却爬不下来的情况,参考1,可能要在setting里添加 SCHEDULER_MEMORY_QUEUE = 'scrapy.squeues.FifoMemoryQueue'

Original: https://blog.csdn.net/weixin_43483799/article/details/126014266
Author: c01dkit
Title: 站点数据收集-Scrapy使用笔记

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/791270/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球