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

前言

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

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

配置

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

基本使用

新建工程

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

在这里插入图片描述

添加目标网站

命令行也会提示进入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:
    	   #发送'Content-Type':'application/x-www-form-urlencoded'的请求
    	   #会自动把请求的body部分处理成arg1=xxx&arg2=xxx的格式
           yield scrapy.FormRequest(
            	url=url, 
            	formdata={
    
    'arg1':'xxx','arg2':'xxx'},
            	callback=self.parse, 
            	meta={
    
    'arg1':1,'arg2':2}
            )
           #发送'Content-Type':'application/json'的请求
           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)作为返回值。爬虫运行时如果发现没有定义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>,即可开始爬取。

再以官网文档为例:

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>来将其保存到文件中,方便后续处理。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) # 获取失败的请求的url
		print(failure.request.cb_kwargs['this_url']) # 通过cb_kwargs,向错误处理函数传参
		
    def parse(self, response, **kwargs):
        url = kwargs['this_url']

进阶使用

缓存(重要)

可以将项目下的settings.py里最下面的HTTP_CACHE_XXX启用,来自动将爬下来的数据缓存在本地。当发现xpath写得不对时可以快速更新结果而不用重新请求网站。使用默认的HTTPCACHE_STORAGE = 'scrapy.extensions.httpcache.FilesystemCacheStorage'时,默认会保存在项目的.scrapy/<crawler name>下。之前大致读了下缓存的代码,大致逻辑是根据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}", # 在headers里设置下代理token
}
def start_requests(self):
	yield scrapy.Request(
		headers = headers, # 设置使用headers,包含token
		meta = meta, # 设置使用代理
		)

一些注意事项

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


  1. https://yuerblog.cc/2018/09/20/scrapy-website-snapshot/ ↩︎

猜你喜欢

转载自blog.csdn.net/weixin_43483799/article/details/126014266