基于Scrapy的爬虫解决方案

导语 | Scrapy是一个较为流行的Python爬虫框架,本文将简单介绍Scrapy的使用方法,并对一些常见问题提出解决方法。对于想快速上手爬虫的初学者来说,本文值得一阅。文章作者:赵宇航,腾讯CSIG研发工程师。

一、背景介绍

笔者在业务中遇到了爬虫需求,由于之前没做过相关的活儿,所以从网上调研了很多内容。但是互联网上的信息比较杂乱,且真真假假,特别不方便,所以完成业务后就想写一篇对初学者友好且较为完整的文章,希望能对阅读者有所帮助。

由于笔者最近Python用得比较熟练,所以就想用Python语言来完成这个任务。经过一番调研,发现Scrapy框架使用者比较多,文档也比较全,所以选择了使用该框架。(其实Scrapy只做了非常简单的封装,对于普通的爬虫任务,使用requests库和bs4库中的BeautifulSoup类就完全能解决了)。

首先简单介绍一下爬虫是什么。爬虫就是从一个或多个URL链接开始,使用某种方法(例如requests库中的函数)获取到该URL对应的网页的内容(一般是HTML格式),然后从该网页的内容中提取出需要记录下来的信息和需要继续爬取的URL链接(例如使用上文中提到的BeautifulSoup类)。之后,再对爬取到的URL链接进行上述同样的操作,直到所有URL链接都被爬取完,爬虫程序结束。

Scrapy的官网【1】,英文版官方文档【2】,第三方的汉化文档(较为简陋和过时)【3】提供如下,感兴趣的读者也可以自行查阅。由于本文重点不在这里,就不在此处对Scrapy进行介绍了。

【1】:https://scrapy.org/

【2】:https://docs.scrapy.org/en/latest/

【3】:https://scrapy-chs.readthedocs.io/zh_CN/0.24/index.html

二、Scrapy使用方法

1. 安装Scrapy库

pip install scrapy


2. 新建一个爬虫项目

在工作目录下输入命令:

scrapy startproject your_project_name

输入该命令后,会在当前目录下新建一个名为your_project_name的文件夹,该文件夹下的文件层级关系如下:

your_project_name|    scrapy.cfg|----your_project_name|    |    __init__.py|    |    items.py|    |    middlewares.py|    |    pipelines.py|    |    settings.py|    |----spiders|    |    |    __init__.py


其中,scrapy.cfg是整个项目的配置文件,spiders目录下存放爬虫的逻辑代码,因为该项目刚建立,还没有写具体的爬虫代码,所以该目录下为空。


3. 生成一个爬虫

在刚刚新建的项目目录下输入命令:

scrapy genspider example www.qq.com

其中example是爬虫的名字,www.qq.com是该爬虫的第一个要爬取的URL链接。

执行该命令后,Scrapy会在spiders目录下生成一个叫example.py的文件,该文件是一个非常基础的爬虫模板。之后要做的事情就是在该py文件里填入具体的爬虫逻辑代码,然后再执行该爬虫脚本就可以了。example.py文件内的代码如下:

import scrapy

class ExampleSpider(scrapy.Spider):    name = 'example'    allowed_domains = ['qq.com']    start_urls = ['http://qq.com/']
    def parse(self, response):        pass


代码中的ExampleSpider就是刚才生成的爬虫类。其中,name是爬虫的名字,allowed_domains是对域名的限制(即该爬虫只会爬取该限制下的URL域名),start_urls是爬虫的初始URL链接,这里面的值是刚才创建爬虫时输入的URL链接,parse函数是默认的解析函数。

4. 运行爬虫

在项目目录下执行命令:

scrapy crawl example

其中example是要运行的爬虫名字。执行该命令后,该框架就会用example爬虫里定义的初始URL链接和解析函数去爬取网页了。

5. 调试爬虫

在写代码的过程中,由于不同网页的源码的组织方式不同,所以需要用一种交互式的方式来访问网页,以此来修改代码。虽然在很多情况下可以通过Chrome浏览器F12的审查模式来查看网页的HTML源码,但是在有些情况下代码中获得的源码和浏览器中看到的却是不一样的,所以交互式访问网页就必不可少了。(也可以通过运行完整爬虫的方式来调试代码,但是效率就有点低下了)。

要想交互式访问网页,需要在项目目录下执行命令:

scrapy shell www.qq.com

使用体验类似于直接在命令行输入python进入Python的交互式界面。

6. 完善解析函数

解析函数的完善是爬虫的核心步骤。解析函数的初始化如下:

def parse(self, response):    pass


其中只有response一个实参,该实参就是访问某个URL链接的返回结果,里面含有该URL链接的HTML源码(该response是对requests.Response类的封装,所以用法类似,但是包含的成员函数更多)。而解析函数parse的作用就是从response中杂乱的HTML源码提取出有价值的信息。

在Scrapy框架中,有两种解析HTML源码的函数,分别是css和xpath。其中css是Scrapy专有的函数,具体用法只能在Scrapy文档中查找,不建议使用;而xpath是一种通用的语言(例如BeautifulSoup类中也能使用),它的一些语法的定义在网上资料更多。xpath的具体用法要讲的话就太多了,所以这里不多做介绍,如果有需要,可以直接去搜索引擎查找相关资料。

如果需要在解析过程中遇到了需要解析的URL链接,则可以直接调用:

yield scrapy.Request(url_str, callback=self.parse)


其中,url_str是需要解析的URL链接的字符串,self.parse是解析函数,这里我使用的是默认的解析函数,当然这里也能使用自定义的解析函数(自定义解析函数的入参出参类型需要和默认解析函数相同)。

值得注意的是:scrapy.Request除了以上俩必须的参数外,还能通过meta字段来传递参数,而参数的获取能通过 response.meta 来实现。


7. 小建议

默认情况下,Scrapy会遵守被爬取网站的robots.txt规则(该文件规定了哪些能爬,哪些不能爬),但往往我们想要爬取的内容都被规定为不能爬取的内容。可以将settings.py文件中的 ROBOTSTXT_OBEY = True 改为 ROBOTSTXT_OBEY = False 来避免这种情况的发生。


三、常见问题


1. 动态网页不能正确解析

上述的简单操作只能解析静态网页,需要动态加载的网页(例如含有Javascript代码的网页)则无法正常解析,因为response里的HTML源码是动态加载之前的页面的源码,而我们需要的大多是动态加载之后的页面。

可以通过在Python中调用Chrome浏览器的方式来处理这个问题。除此之外,还能使用Chrome浏览器的headless模式。使用了该模式之后,Chrome浏览器并不会真的被调用,但是Python中能获取到和浏览器相同的返回结果,而浏览器中返回的结果就是动态加载之后的页面。

不过,要使用这个方法,必须在机器上安装Chrome浏览器和对应版本的Chrome驱动程序。安装完成之后,在middlewares.py文件中加入以下代码:

from selenium import webdriverfrom scrapy.http import HtmlResponse

class JavaScriptMiddleware:    def process_request(self, request, spider):        option = webdriver.ChromeOptions()        option.add_argument('--headless')        option.add_argument('--no-sandbox')        option.add_argument('--disable-gpu')        driver = webdriver.Chrome(options=option, executable_path=chrome_driver_path_str)        driver.get(request.url)        js = 'var q=document.documentElement.scrollTop=10000'        driver.execute_script(js)        body = driver.page_source        return HtmlResponse(driver.current_url, body=body, encoding='utf-8', request=request)

除此之外,还要在settings.py文件中加入以下代码:

DOWNLOADER_MIDDLEWARES = {    'your_project_name.middlewares.JavaScriptMiddleware': 543,}


经过这两处修改之后,爬虫脚本里的所有request请求都会通过Chrome headless浏览器包装后再发向要爬取的URL链接。

2. 防爬虫之修改header

很多网站都有各自的反爬虫机制,但是最基础的一种方式是检查请求的HTTP包里面的header是否正常。其中经常检查的一个字段是User-Agent,User-Agent字段指的是浏览器的型号。反爬虫机制会检查该字段是否为普通的浏览器,而普通的爬虫程序是不会修饰该字段的。如果不显式将该字段设为某种浏览器型号,就容易触发反爬虫,从而不能正常地获得数据。

要想修改Scrapy里的user-agent字段,可以在settings.py文件里添加以下代码:

USER_AGENT = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/84.0.4147.89 Safari/537.36'

添加完该代码后,Scrapy在发起request请求时就会将上面的值替换到header中的User-Agent中。

3. 反爬虫之IP池

在很多时候,爬取网站时一开始是能正常获得数据的,但是爬着爬着,就不能正常地获得数据了。一个很大的可能是IP被该网站封禁了。每个网站封IP的策略都不一样,但是总体来说其实就是该IP访问该网站的频率太高,网站害怕该访问是恶意攻击或者担心服务器承受不了大量的访问从而直接封禁该IP。

应对方式也非常粗暴,那就是用代理IP去爬虫。网站封一个IP,我就用另外的IP去访问,只要我IP足够多,就总能获取到我想要的所有数据。而正好互联网上就有服务商提供这种IP服务。网上大致分为免费和付费两种服务,其中免费提供商提供的IP质量非常低,有不小的概率是直接不能用的,所以这里不推荐使用免费服务。至于付费服务商网上有很多家都挺靠谱的,本文里使用的名为“快代理”的服务商,下面提供的代码也是只针对该特定厂家的。不同服务商使用IP池的方式都不一样,具体使用方法还是以各自的官方文档为主。

在“快代理”上购买IP套餐后,在middleware.py文件中添加一下代码:

from w3lib.http import basic_auth_headerimport requests

class ProxyDownloaderMiddleware:    username = 'your_username'    password = 'your_password'    api_url = 'https://dps.kdlapi.com/api/getdps/?orderid=your_orderid&num=1&pt=1&dedup=1&sep=1'    proxy_ip_list = []    list_max_len = 20
    def update_ip(self):        if len(self.proxy_ip_list) != self.list_max_len:            ip_str = requests.get('https://dps.kdlapi.com/api/getdps/?orderid=your_orderid&num={}&pt=1&dedup=1&sep=3'.format(self.list_max_len)).text            self.proxy_ip_list = ip_str.split(' ')        while True:            try:                proxy_ip = self.proxy_ip_list.pop(0)                proxies = {                    'http': 'http://{}:{}@{}'.format(self.username, self.password, proxy_ip),                    'https': 'http://{}:{}@{}'.format(self.username, self.password, proxy_ip)                }                requests.get('http://www.baidu.com', proxies=proxies, timeout=3.05)                self.proxy_ip_list.append(proxy_ip)                return            except Exception as e:                self.proxy_ip_list.append(requests.get(self.api_url).text)
    def process_request(self, request, spider):        self.update_ip()        request.meta['proxy'] = 'http://{}'.format(self.proxy_ip_list[-1])        # 用户名密码认证        request.headers['Proxy-Authorization'] = basic_auth_header(self.username, self.password)        return None


其中username,password,order id都是“快代理”中使用IP所要用的参数。上面的代码维护了一个大小为20的IP池,每次要使用时就提取第一个IP并先要检查该IP是否已经失效,如果失效了就丢弃并补充新的IP。Scrapy每次发起request请求时,会经过该proxy层的封装,但要想正常使用,还得在settings.py文件中添加以下代码:

DOWNLOADER_MIDDLEWARES = {    'your_project_name.middlewares.ProxyDownloaderMiddleware': 100,}


在上文爬取动态页面的相关内容中也修改了这个 DOWNLOADER_MIDDLEWARES 这个字典。该字典中的key和value分别是在middlewares.py文件中添加的类和封装request包的顺序。如果要同时使用动态页面爬取和IP池,那么settings.py文件的该参数应该如下所示:

DOWNLOADER_MIDDLEWARES = {    'your_project_name.middlewares.JavaScriptMiddleware': 543,    'your_project_name.middlewares.ProxyDownloaderMiddleware': 100,}


其中100 < 543,代表request请求要先经过代理封装再经过动态加载封装,最后才发送给目标URL链接。


四、结语

本文简单介绍了下Scrapy框架的相关用法,并针对爬虫中常遇见的问题给出了基于Scrapy框架的解决方案,希望能对读者有所帮助,也欢迎大家在评论区与我交流。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值