报名参加CloudWeGo黑客松,奖金直推双丰收! 了解详情
写点什么

基于 Scrapy 的爬虫解决方案

  • 2021-06-23
  • 本文字数:4948 字

    阅读完需:约 16 分钟

基于Scrapy的爬虫解决方案

一、背景介绍


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


由于笔者最近 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 使用方法


安装 Scrapy 库


pip install scrapy
复制代码


新建一个爬虫项目


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 目录下存放爬虫的逻辑代码,因为该项目刚建立,还没有写具体的爬虫代码,所以该目录下为空。


生成一个爬虫


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


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 函数是默认的解析函数。


运行爬虫


在项目目录下执行命令:


scrapy crawl example


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


调试爬虫


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


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


scrapy shell www.qq.com


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


完善解析函数


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


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 来实现。


小建议


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


三、常见问题


动态网页不能正确解析


上述的简单操作只能解析静态网页,需要动态加载的网页(例如含有 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 链接。


防爬虫之修改 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 中。


反爬虫之 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 链接。



头图:Unsplash

作者:赵宇航

原文:https://mp.weixin.qq.com/s/-jCxnhzo-G9fzZNT-Azp7g

原文:基于 Scrapy 的爬虫解决方案

来源:云加社区 - 微信公众号 [ID:QcloudCommunity]

转载:著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

2021-06-23 08:002314

评论

发布
暂无评论
发现更多内容

知识管理在企业竞争发展中的作用

小炮

知识管理

数仓如何设置大小写不敏感函数

华为云开发者联盟

MySQL DWS GaussDB(DWS) 大小写不敏感函数 GUC参数

云原生时代已来,计算机教育如何因「云」而变?

阿里云弹性计算

云原生 ECS 计算机教育

对于金融机构而言,为什么选择私有化IM比企业微信、钉钉更好?

BeeWorks

一首古诗带来的图数据库大冒险

NebulaGraph

知识图谱 实践案例 分布式图数据库

2022年2月视频行业用户洞察:冬奥吸引全民关注拉动视频平台出圈

易观分析

短视频 冬奥会

web前端培训如何用CSS来实现透明方格

@零度

CSS web前端

Meetup预告|云原生时代热门监控利器解析与应用

云智慧AIOps社区

运维 云原生 安全 监控工具

SeaTunnel 在 oppo 的特征平台实践 | ETL 平台数据处理集成

Apache SeaTunnel

Big Data 大数据平台 大数据开发 apache 社区 opensource

API成数据安全最大风险敞口 如何打赢数字时代的“数据保卫战”?

BeeWorks

中文版Postmna

Liam

Jmeter Postman 开发工具 swagger 测试工具

ABAP excel数据上传函数改造

Jasen Ye

Excel upload abap

【直播回顾】OpenHarmony知识赋能第四期第三课——I2C驱动开发

OpenHarmony开发者

OpenHarmony 驱动开发

大数据培训连续登录经典面试案例

@零度

大数据

你可能需要知道的API接口文档神器

ModStart开源

云计算时代服务器运维就用行云管家!功能齐全,福利多多!

行云管家

云计算 云管平台 服务器运维

如何升级到 React 18

CRMEB

OceanBase 源码解读(九):存储层代码解读之「宏块存储格式」

OceanBase 数据库

源码剖析 oceanbase

利用亚马逊云科技整个自用免费网盘

亚马逊云科技 (Amazon Web Services)

Builder 专栏

手把手教你搭建博客

亚马逊云科技 (Amazon Web Services)

从多快好省到好快省多,您的项目管理走对了吗?

禅道项目管理

项目管理

科创人·36氪副总裁王坤:企服产品应重视使用者体验,36氪将推出中国版「魔力象限」

科创人

企业服务

【IT运维】传统运维与云运维到底有什么不同呢?

行云管家

云计算 IT运维 云运维

速度和质量不可兼得,为什么DevOps落地如此困难?

飞算JavaAI开发助手

郑泽康:一名热爱技术的“保安”|OneFlow U

OneFlow

人工智能 机器学习 深度学习 程序人生 cuda

容器化 | K8s 部署 RadonDB MySQL Operator 和集群

RadonDB

MySQL 数据库 Kubernetes 高可用 RadonDB

揭秘!网易有道技术岗实习生都在做什么?

有道技术团队

招聘 实习 网易有道

华云数据与龙蜥社区完成产品兼容互认证,携手推动开源生态体系建设与发展

OpenAnolis小助手

云计算 开源社区 生态体系 华云数据 兼容互认证

Facebook 开源 Golang 实体框架 Ent 现已支持 TiDB

PingCAP

WebGL 及其在 WebRTC 中的应用

ZEGO即构

WebRTC WebGL 实时音视频 即构科技

【OH干货】 告别代码,让Openharmony软总线测试用例跑起来!!!

拓维信息

分布式软总线 OpenHarmony

基于Scrapy的爬虫解决方案_文化 & 方法_云加社区_InfoQ精选文章