一. Scrapy简介、架构、数据流和项目结构
二. Scrapy入门
1. Scrapy架构由哪些部分组成?
1.1 Scrapy简介
Scrapy是:由Python语言开发的一个快速、高层次的屏幕抓取和web抓取框架,用于抓取web站点并从页面中提取结构化的数据。
Scrapy依赖包
Scrapy 是用纯python编写的,它依赖于几个关键的python包(以及其他包):
lxml 一个高效的XML和HTML解析器
parsel ,一个写在lxml上面的html/xml数据提取库,
w3lib ,用于处理URL和网页编码的多用途帮助程序
twisted 异步网络框架
cryptography 和 pyOpenSSL ,处理各种网络级安全需求
1.2 Scrapy架构
Engine。引擎,处理整个系统的数据流处理、触发事务,是整个框架的核心。
Item。项目,它定义了爬取结果的数据结构,爬取的数据会被赋值成该Item对象。
Scheduler。调度器,接受引擎发过来的请求并将其加入队列中,在引擎再次请求的时候将请求提供给引擎。
Downloader。下载器,下载网页内容,并将网页内容返回给蜘蛛。
Spiders。蜘蛛,其内定义了爬取的逻辑和网页的解析规则,它主要负责解析响应并生成提取结果和新的请求。
Item Pipeline。项目管道,负责处理由蜘蛛从网页中抽取的项目,它的主要任务是清洗、验证和存储数据。
Downloader Middlewares。下载器中间件,位于引擎和下载器之间的钩子框架,主要处理引擎与下载器之间的请求及响应。
Spider Middlewares。蜘蛛中间件,位于引擎和蜘蛛之间的钩子框架,主要处理蜘蛛输入的响应和输出的结果及新的请求。
1.3 数据流
Scrapy中的数据流由引擎控制,数据流的过程如下。
Engine首先打开一个网站,找到处理该网站的Spider,并向该Spider请求第一个要爬取的URL。
Engine从Spider中获取到第一个要爬取的URL,并通过Scheduler以Request的形式调度。
Engine向Scheduler请求下一个要爬取的URL。
Scheduler返回下一个要爬取的URL给Engine,Engine将URL通过Downloader Middlewares转发给Downloader下载。
一旦页面下载完毕,Downloader生成该页面的Response,并将其通过Downloader Middlewares发送给Engine。
Engine从下载器中接收到Response,并将其通过Spider Middlewares发送给Spider处理。
Spider处理Response,并返回爬取到的Item及新的Request给Engine。
Engine将Spider返回的Item给Item Pipeline,将新的Request给Scheduler。
重复第二步到最后一步,直到Scheduler中没有更多的Request,Engine关闭该网站,爬取结束。
通过多个组件的相互协作、不同组件完成工作的不同、组件对异步处理的支持,Scrapy最大限度地利用了网络带宽,大大提高了数据爬取和处理的效率。
1.4 项目结构
Scrapy框架和pyspider不同,它是通过命令行来创建项目的,代码的编写还是需要IDE。项目创建之后,项目文件结构如下所示:
这里各个文件的功能描述如下。
-
scrapy.cfg:它是Scrapy项目的配置文件,其内定义了项目的配置文件路径、部署相关信息等内容。
-
items.py:它定义Item数据结构,所有的Item的定义都可以放这里。
-
pipelines.py:它定义Item Pipeline的实现,所有的Item Pipeline的实现都可以放这里。
-
settings.py:它定义项目的全局配置。
-
middlewares.py:它定义Spider Middlewares和Downloader Middlewares的实现。
-
spiders:其内包含一个个Spider的实现,每个Spider都有一个文件。
note:在指定文件夹下打开cmd窗口,输入 tree/f 命令可以查看该文件夹结构
2. Scrapy入门
先安装以下两个库,注意这些库可能有版本更新,旧版本可能会报错,及时安装新版本,尤其是fake-useragent
库。
pip install scrapy # 构建和运行网络爬虫的强大的 Python 框架
pip install fake-useragent # 生成伪装用户代理的 Python 库,这里先安装,后面会讲到
Note: 推荐在anaconda prompt和anaconda powershell prompt窗口中执行如下命令,或者在cmd窗口的base环境中运行,cmd窗口中激活base环境命令如下:
conda activate base
否则如下创建scrapy项目的命令会报错,如下所示,提示默认conda环境未激活
C:\Users\sun78\Desktop\视频 python\9>python
Python 3.8.8 (default, Apr 13 2021, 15:08:03) [MSC v.1916 64 bit (AMD64)] :: Anaconda, Inc. on win32
Warning:
This Python interpreter is in a conda environment, but the environment has
not been activated. Libraries may fail to load. To activate this environment
please see https://conda.io/activation
Type "help", "copyright", "credits" or "license" for more information.
>>>
2.1 创建项目
安装完成后就可以使用scrapy命令来创建项目,如下
scrapy startproject sougouspider # gousearchspide就是项目名
如下述树状文件结构所示,创建的sousouspider项目文件夹下面包含scrapy.cfg文件和sougouspider子文件夹,子文件夹中包含items.py,middlewares.py,pipelines.py等文件以及spiders子孙文件夹。
2.2 创建Spider
Spider是自己定义的Class,Scrapy用它来从网页里抓取内容,并解析抓取的结果。不过这个Class必须继承Scrapy提供的Spider类scrapy.Spider,还要定义Spider的名称和起始请求,以及怎样处理爬取后的结果的方法。
也可以使用命令行创建一个Spider。比如要生成quotes这个Spider,可以执行如下命令:
cd sougouspider # 进入项目文件夹中的sougouspider子文件夹
scrapy genspider sgspider weixin.sogou.com # sgspider为spider名称,后是网站域名
进入刚才创建的sougouspider文件夹,然后执行genspider命令。第一个参数是Spider的名称,第二个参数是网站域名。执行完毕之后,spiders文件夹中多了一个sgspider.py,它就是刚刚创建的Spider
sgspider内容如下所示:
import scrapy
class SgspiderSpider(scrapy.Spider):
name = 'sgspider'
allowed_domains = ['weixin.sogou.com']
start_urls = ['http://weixin.sogou.com/']
def parse(self, response):
pass
这里有三个属性——
name
、allowed_domains
和start_urls
,还有一个方法parse
。
name
,它是每个项目唯一的名字,用来区分不同的Spider。
allowed_domains
,它是允许爬取的域名,如果初始或后续的请求链接不是这个域名下的,则请求链接会被过滤掉。
start_urls
,它包含了Spider在启动时爬取的url列表,初始请求是由它来定义的。
parse
,它是Spider的一个方法。默认情况下,被调用时start_urls
里面的链接构成的请求完成下载执行后,返回的响应就会作为唯一的参数传递给这个函数。该方法负责解析返回的响应、提取数据或者进一步生成要处理的请求。
2.3 创建Item
Item是保存爬取数据的容器,它的使用方法和字典类似。不过,相比字典,Item多了额外的保护机制,可以避免拼写错误或者定义字段错误。
创建Item需要继承
scrapy.Item
类,并且定义类型为scrapy.Field
的字段。观察目标网站,我们可以获取到到内容有url,page等。
scrapy默认创建的items.py内容如下
# Define here the models for your scraped items
#
# See documentation in:
# https://docs.scrapy.org/en/latest/topics/items.html
import scrapy
class SougouspiderItem(scrapy.Item):
# define the fields for your item here like:
# name = scrapy.Field()
pass
定义Item,此时将items.py修改如下:
# Define here the models for your scraped items
#
# See documentation in:
# https://docs.scrapy.org/en/latest/topics/items.html
import scrapy
class SougouspiderItem(scrapy.Item):
# define the fields for your item here like:
# name = scrapy.Field()
visit_url = scrapy.Field()
page = scrapy.Field()
rank = scrapy.Field()
title = scrapy.Field()
# pass
其中visit_url,page,rank和 title 是 sgspider中定义的变量
2.4 修改settings.py
创建sougouspider默认生成的settings.py的内容如下
# Scrapy settings for sougouspider project
#
# For simplicity, this file contains only settings considered important or
# commonly used. You can find more settings consulting the documentation:
#
# https://docs.scrapy.org/en/latest/topics/settings.html
# https://docs.scrapy.org/en/latest/topics/downloader-middleware.html
# https://docs.scrapy.org/en/latest/topics/spider-middleware.html
BOT_NAME = 'sougouspider'
SPIDER_MODULES = ['sougouspider.spiders']
NEWSPIDER_MODULE = 'sougouspider.spiders'
# Crawl responsibly by identifying yourself (and your website) on the user-agent
#USER_AGENT = 'sougouspider (+http://www.yourdomain.com)'
# Obey robots.txt rules
ROBOTSTXT_OBEY = True
# Configure maximum concurrent requests performed by Scrapy (default: 16)
#CONCURRENT_REQUESTS = 32
# Configure a delay for requests for the same website (default: 0)
# See https://docs.scrapy.org/en/latest/topics/settings.html#download-delay
# See also autothrottle settings and docs
#DOWNLOAD_DELAY = 3
# The download delay setting will honor only one of:
#CONCURRENT_REQUESTS_PER_DOMAIN = 16
#CONCURRENT_REQUESTS_PER_IP = 16
# Disable cookies (enabled by default)
#COOKIES_ENABLED = False
# Disable Telnet Console (enabled by default)
#TELNETCONSOLE_ENABLED = False
# Override the default request headers:
#DEFAULT_REQUEST_HEADERS = {
# 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
# 'Accept-Language': 'en',
#}
# Enable or disable spider middlewares
# See https://docs.scrapy.org/en/latest/topics/spider-middleware.html
#SPIDER_MIDDLEWARES = {
# 'sougouspider.middlewares.SougouspiderSpiderMiddleware': 543,
#}
# Enable or disable downloader middlewares
# See https://docs.scrapy.org/en/latest/topics/downloader-middleware.html
#DOWNLOADER_MIDDLEWARES = {
# 'sougouspider.middlewares.SougouspiderDownloaderMiddleware': 543,
#}
# Enable or disable extensions
# See https://docs.scrapy.org/en/latest/topics/extensions.html
#EXTENSIONS = {
# 'scrapy.extensions.telnet.TelnetConsole': None,
#}
# Configure item pipelines
# See https://docs.scrapy.org/en/latest/topics/item-pipeline.html
#ITEM_PIPELINES = {
# 'sougouspider.pipelines.SougouspiderPipeline': 300,
#}
# Enable and configure the AutoThrottle extension (disabled by default)
# See https://docs.scrapy.org/en/latest/topics/autothrottle.html
#AUTOTHROTTLE_ENABLED = True
# The initial download delay
#AUTOTHROTTLE_START_DELAY = 5
# The maximum download delay to be set in case of high latencies
#AUTOTHROTTLE_MAX_DELAY = 60
# The average number of requests Scrapy should be sending in parallel to
# each remote server
#AUTOTHROTTLE_TARGET_CONCURRENCY = 1.0
# Enable showing throttling stats for every response received:
#AUTOTHROTTLE_DEBUG = False
# Enable and configure HTTP caching (disabled by default)
# See https://docs.scrapy.org/en/latest/topics/downloader-middleware.html#httpcache-middleware-settings
#HTTPCACHE_ENABLED = True
#HTTPCACHE_EXPIRATION_SECS = 0
#HTTPCACHE_DIR = 'httpcache'
#HTTPCACHE_IGNORE_HTTP_CODES = []
#HTTPCACHE_STORAGE = 'scrapy.extensions.httpcache.FilesystemCacheStorage'
修改后的settings.py
BOT_NAME = 'sougouspider'
SPIDER_MODULES = ['sougouspider.spiders']
NEWSPIDER_MODULE = 'sougouspider.spiders'
# Obey robots.txt rules
ROBOTSTXT_OBEY = False # 将False改为True
# Disable cookies (enabled by default)
COOKIES_ENABLED = True # 将False改为True
# Configure item pipelines
# See https://docs.scrapy.org/en/latest/topics/item-pipeline.html
ITEM_PIPELINES = {
'sougouspider.pipelines.SougouspiderPipeline': 300,
}
REDIRECT_ENABLED = False
HTTPERROR_ALLOWED_CODES = [302]
注意:pipelines.py和middlewares.py文件使用原默认内容。关于__init__.py , 在Python工程里,当python检测到一个目录下存在__init__.py文件时,python就会把它当成一个模块(module)。__init__.py可以是一个空文件,也可以有非常丰富的内容。
2.5 修改默认生成的spider文件
修改后的sgspider.py
from sougouspider.items import SougouspiderItem
from IP.free_ip import get_random_proxy
from IP.get_cookies import get_new_cookies,get_new_headers
import scrapy
import time
import random
from tqdm import tqdm
class Sgspiderspider(scrapy.Spider):
name = 'sgspider'
allowed_domains = ['weixin.sogou.com']
start_urls = ['https://weixin.sogou.com/weixin?&query=Python爱好者社区&type=2&ie=utf8']
def start_requests(self):
headers = get_new_headers()
print('请求的headers',headers)
for url in self.start_urls:
# 获取代理IP
proxy = 'http://' + str(get_random_proxy())
print('请求的proxy',proxy)
yield scrapy.Request(url=url,
callback=self.parse,
headers=headers,
meta={'http_proxy': proxy})
def parse(self, response):
headers_new = get_new_headers()
print('第二次请求的headers',headers_new)
cookies_new = get_new_cookies()
print('请求的cookies_new',cookies_new)
# 获取当前页码
current_page = int(response.xpath('//div[@id="pagebar_container"]/span/text()').extract_first())
print('response的内容',response)
print('reponse成功被解析,此时的页码',current_page)
# 解析当前页面
#for i, a in enumerate(response.xpath('//div[contains(@class,"news-box")]/div[@class="txt-box"]/h3/a')):
for i, a in enumerate(response.xpath('//div[contains(@class,"txt-box")]/h3/a')):
# 获取标题,去除空格和换行符
# title = ''.join(a.xpath('./em/text() | ./text()').extract()).replace(' ', '').replace('\n', '')
print('i和a分别为',i,a)
title = a.xpath('.//text()').extract() # 获取文本字符串列表
# string(.)函数会得到所指元素的所有节点文本内容,这些文本讲会被拼接成一个字符串
# title = a.xpath('string(.)')
print('页面标题是',title)
if title:
item = SougouSpiderItem()
# 获取访问链接(①非跳转链接②跳转链接)、页码、行数、标题
if a.xpath('@href').extract_first().startswith('/link'):
item['visit_url'] = 'https://weixin.sogou.com' + a.xpath('@href').extract_first() # 提取链接
else:
item['visit_url'] = a.xpath('@href').extract_first()
item['page'] = current_page
item['rank'] = i + 1
item['title'] = title
yield item
# 控制爬取频率
#time_wait=random.randint(8, 10)
#print('等待时间',time_wait)
#time.sleep(time_wait)
time_wait=random.randint(40, 50)
for i in tqdm(range(time_wait)):
time.sleep(0.2)
# 获取“下一页”的链接
p = response.xpath('//div[@id="pagebar_container"]/a[@id="sogou_next"]')
print('准备解析下一页的网址',p)
if p:
p_url = 'https://weixin.sogou.com/weixin' + str(p.xpath('@href').extract_first())
print('下一页的网址',p_url)
proxy = 'http://' + str(get_random_proxy())
yield scrapy.Request(url=p_url,
callback=self.parse,
headers=headers_new,
cookies=cookies_new,
meta={'http_proxy': proxy})
为了应对网站的反爬虫,采用了更换动态ip和伪装header的措施,采用如下代码将更换动态ip和伪装header的模块导入到在上述spider文件中。(将模块命名为IP,将free_ip.py和get_cookies.py放入该IP文件夹中)
from IP.free_ip import get_random_proxy
from IP.get_cookies import get_new_cookies,get_new_headers
free_ip.py
# coding=utf-8
import requests
proxypool_url = 'http://127.0.0.1:5555/random'
def get_random_proxy():
response = requests.get(proxypool_url)
try:
if response.status_code == 200:
return response.text.strip()
except ConnectionError:
return None
if __name__ == '__main__':
print(get_random_proxy())
注意:上述文件中使用了ip代理池,代理池的构建参考【学习笔记】WSL2、Docker以及docker-compose安装及环境配置_bu懂就问的博客-优快云博客_wsl2和docker
利用scrapy进行爬虫之前需要开启docker,并在代理池相应文件夹下(本案例中是ProxyPool-master文件夹)打开cmd命令行窗口,运行以下命令才能够在 http://127.0.0.1:5555/random 端口获取代理ip,上述free_ip.py才能够正常执行
docker-compose up
崔庆才代理池github地址如下,github地址
get_cookies.py
# coding=utf-8
from IP.free_ip import get_random_proxy
from fake_useragent import UserAgent
import requests
ua = UserAgent().random
def get_new_headers():
headers = {"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9",
"Accept-Encoding": "gzip, deflate, br",
"Accept-Language": "zh-CN,zh;q=0.9,en;q=0.8",
"User-Agent": ua}
return headers
def get_new_cookies():
url = 'https://v.sogou.com/v?ie=utf8&query=&p=40030601'
proxies = {"http": "http://" + get_random_proxy()}
headers = {'User-Agent': ua}
rst = requests.get(url=url,
headers=headers,
allow_redirects=False,
proxies=proxies)
cookies = rst.cookies.get_dict()
return cookies
if __name__ == '__main__':
print(get_new_cookies())
注意:上述get_cookies.py函数是为了生成cookie信息,其中除了导入get_random_proxy()函数生成代理ip之外,还从fake_useragent库导入了UserAgent函数,user-agent可以由fake_useragent随机生成。cookie中的部分信息是基于代理ip访问搜狗视频获取的。
fake_useragent库的安装可以使用如下命令,github地址
pip install fake-useragent
2.6 运行scrapy框架
本案例在运行之前需要
1. 开启docker软件
2. 在ProxyPool-master文件夹中执行 docker-compose up 获取动态代理ip
本案例中ProxyPool-master文件夹位于
D:\software\ProxyPool-master
3. 修改项目爬虫文件中的查询关键词。本案例中,爬虫文件位于
C:\Users\sun78\Desktop\视频 python\scrapy_peform\sougouspider\sougouspider\spiders
4. 之后可以在scrapy项目文件夹中执行下述命令
scrapy crawl sgspider -o XXX.json # XXX.json是爬虫生成的json文件名
scrapy crawl sgspider -o 1.json #推荐使用1.json命名
5. 运行完之后会将items.py中的变量以字典的形式保存为.json文件,其中中文信息尚未进行utf8转码,可以编写python脚本进一步进行数据清洗和转换。下面的脚本将其转换成markdown链接格式。执行以下命令
python conver_json.py
conver_json.py
import json
filename = 'Python爱好者社区.json'
with open(filename) as f_obj:
numbers=json.load(f_obj)
print(len(numbers))
for i in range(len(numbers)):
e = numbers[i]
print('['+' '.join(e['title'])+']'+'('+ e['visit_url']+ ')'+'\n')
with open('Python爱好者社区.txt', 'a') as new_file:
# new_file.write(str(e['title'])+'('+ e['visit_url']+ ')'+'\n')
new_file.write('['+' '.join(e['title'])+']'+'('+ e['visit_url']+ ')'+'\n')
#
#print(i)
参考资料和链接
【scrapy爬虫】最新sogou搜狗搜索 机智操作绕过反爬验证码(搜狗微信公众号文章同理)_彡千的博客-优快云博客
GitHub - hellysmile/fake-useragent: up to date simple useragent faker with real world database
GitHub - Python3WebSpider/ProxyPool: An Efficient ProxyPool with Getter, Tester and Server
崔庆才. python3网络爬虫开发实战