scrapy框架使用总结
scrapy总结
1 scrapy项目开发流程
1.1 创建项目命令
scrapy startproject guokespider
1.2 创建一个爬虫
在终端中,先进入到爬虫项目目录下, 然后执行生成爬虫命令,格式scrapy genspider 爬虫名 域名
scrapy genspider guoke www.guokr.com
1.3 数据建模
在items.py
文件中进行建模
1.4 编写爬虫
- 修改
start_urls
- 检查修改
allowed_domains
- 编写爬虫解析响应方法
- 在
pipelines.py
文件中,创建处理数据的管道,用于保存数据 - 修改
settings.py
配置文件, 注册管道
1.5 运行爬虫
scrapy crawl guoke --nolog
下面来简单介绍一下各个主要文件的作用:
scrapy.cfg :项目的配置文件
mySpider/ :项目的Python模块,将会从这里引用代码
mySpider/items.py :项目的目标文件
mySpider/pipelines.py :项目的管道文件
mySpider/settings.py :项目的设置文件
mySpider/spiders/ :存储爬虫代码目录
2 scrapy的运行流程
Scrapy Engine(引擎)
: 负责Spider
、ItemPipeline
、Downloader
、Scheduler
中间的通讯,信号、数据传递等。Scheduler(调度器)
: 存放request对象,把request对象>引擎>下载器中间件==>下载器,并按照一定的方式进行整理排列,入队,当引擎
需要时,交还给引擎
。Downloader(下载器)
:负责下载Scrapy Engine(引擎)
发送的所有Requests请求,并将其获取到的Responses交还给Scrapy Engine(引擎)
,由引擎
交给Spider
来处理,生成response对象>下载器中间件>引擎>爬虫中间件>爬虫Spider(爬虫)
:提取url和数据,提取的url会转换成request对象>爬虫中间件>引擎>调度器,而提取的数据通过引擎交给管道,即数据>爬虫中间件>引擎>管道Item Pipeline(管道)
:数据处理,保存数据。它负责处理Spider
中获取到的Item,并进行进行后期处理(详细分析、过滤、存储等)的地方.Downloader Middlewares(下载中间件)
:你可以当作是一个可以自定义扩展下载功能的组件。Spider Middlewares(Spider中间件)
:你可以理解为是一个可以自定扩展和操作引擎
和Spider
中间通信
的功能组件(比如进入Spider
的Responses;和从Spider
出去的Requests)
3 代码案例
3.1 新建scrapy项目
- 创建爬虫项目,命令:scrapy startproject 项目名称
- 创建爬虫文件,命令:scrapy genspider 文件名称 域名
3.2 构建项目模型
import scrapy
class BookItem(scrapy.Item):
# define the fields for your item here like:
# name = scrapy.Field()
book_cate01 = scrapy.Field()
book_cate02 = scrapy.Field()
book_cate03 = scrapy.Field()
book_href = scrapy.Field()
book_name = scrapy.Field()
book_store = scrapy.Field()
book_num = scrapy.Field()
构建的字段模型 可以像 python字典一样使用
在爬虫文件中suning.py
导入定义的字段项目模型:
import scrapy
from book.items import BookItem
然后再解析方法parse
中实例项目模型即可当做字典使用:
def parse(self, response, **kwargs):
"""解析响应中的数据
:param **kwargs:
"""
# 一级分类列表
div_list = response.xpath('//div[@class="menu-item"]')[:7]
# 二级分类列表
div_sub_list = response.xpath('//div[@class="menu-list"]/div[@class="menu-sub"]')
print(div_sub_list)
for div in div_list[:1]:
item = BookItem()
item['book_cate01'] = div.xpath('.//h3/a/text()').extract_first()
pass
3.3 启用管道
class SomethingPipeline(object):
def __init__(self):
# 可选实现,做参数初始化等
# doing something
def process_item(self, item, spider):
# item (Item 对象) – 被爬取的item
# spider (Spider 对象) – 爬取该item的spider
# 这个方法必须实现,每个item pipeline组件都需要调用该方法,
# 这个方法必须返回一个 Item 对象,被丢弃的item将不会被之后的pipeline组件所处理。
return item
def open_spider(self, spider):
# 在爬虫开始的时候仅执行一次
# 可选实现,当spider被开启时,这个方法被调用。
def close_spider(self, spider):
# 在爬虫结束的时候仅执行一次
# 可选实现,当spider被关闭时,这个方法被调用
-
启用一个Item Pipeline组件 为了启用Item Pipeline组件,需要在 settings.py文件中ITEM_PIPELINES 注册:
# 分配给每个类的整型值,确定了他们运行的顺序,item按数字从低到高的顺序,通过pipeline,通常将这些数字定 ITEM_PIPELINES = { 'book.pipelines.BookPipeline': 300, }
3.4 数据持久化
以mongodb
为例,将数据存入mongodb:
import pymongo
class BookPipeline(object):
def open_spider(self, spider):
"""
爬虫开始只执行一次
:param spider:
:return:
"""
# 创建mongodb的数据库连接
self.client = pymongo.MongoClient(host='192.168.99.100', port=27018)
# 选择数据库和数据表
self.cursor = self.client['suning']['book']
# 清空数据
self.cursor.delete_many({})
def process_item(self, item, spider):
# 保存到数据库中
self.cursor.insert_one(dict(item))
return item
def close_spider(self, spider):
"""
爬虫结束时只执行一次
:param spider:
:return:
"""
# 关闭连接
self.client.close()
print('爬虫结束')
3.5 数据解析
# -*- coding: utf-8 -*-
from copy import deepcopy
from pprint import pprint
import scrapy
from book.items import BookItem
class SuningSpider(scrapy.Spider):
name = 'suning'
allowed_domains = ['suning.com']
start_urls = ['https://book.suning.com/']
# 当前页的书籍计数
book_num = 0
def parse(self, response, **kwargs):
"""解析响应中的数据
:param **kwargs:
"""
# 一级分类列表
div_list = response.xpath('//div[@class="menu-item"]')[:7]
# 二级分类列表
div_sub_list = response.xpath('//div[@class="menu-list"]/div[@class="menu-sub"]')
print(div_sub_list)
for div in div_list[:1]:
item = BookItem()
item['book_cate01'] = div.xpath('.//h3/a/text()').extract_first()
# 一级分类下的元素列表
sub_div = div_sub_list[div_list.index(div)]
# 一级分类的所有二级分类和三级分类
p_list = sub_div.xpath('./div[@class="submenu-left"]/p')
for p in p_list[:1]:
# 二级分类
item['book_cate02'] = p.xpath('./a/text()').extract_first()
# 三级分类元素列表
li_list = p.xpath('./following-sibling::ul[1]/li')
for li in li_list[:1]:
item['book_cate03'] = li.xpath('./a/text()').extract_first()
item['book_href'] = li.xpath('./a/@href').extract_first()
yield scrapy.Request(
item['book_href'],
callback=self.parse_book_list,
meta={'item': deepcopy(item)}
)
# 获取当前页的后半部分的数据
next_part_url = "https://list.suning.com/emall/showProductList.do?ci={}&pg=03&cp=0&il=0&iy=0&adNumber=0&n=1&ch=4&prune=0&sesab=ACBAAB&id=IDENTIFYING&paging=1&sub=0"
ci = item['book_href'].split('-')[1]
next_part_url = next_part_url.format(ci)
print(next_part_url)
yield scrapy.Request(
next_part_url,
callback=self.parse_book_list,
meta={'item': deepcopy(item)}
)
print('=' * 10)
next_part_url.format(ci)
def parse_book_list(self, response):
"""解析response中的所有book"""
print(1)
item = response.meta.get('item')
li_list = response.xpath('//li[contains(@class,"product book")]')
for li in li_list:
item['book_name'] = li.xpath('.//p[@class="sell-point"]/a/text()').extract_first().strip()
item['book_href'] = li.xpath('.//p[@class="sell-point"]/a/@href').extract_first()
item['book_store'] = li.xpath('.//p[contains(@class, "seller oh no-more")]/a/text()').extract_first()
yield scrapy.Request(
response.urljoin(item['book_href']),
callback=self.parse_book_detail,
meta={'item': deepcopy(item)}
)
# TODO 分页
def parse_book_detail(self, response):
"""解析书籍信息"""
# get date
item = response.meta.get('item')
self.book_num += 1
item['book_num'] = self.book_num
yield item
4 CrawlSpider的高阶使用
通过命令创建 CrawlSpider爬虫的代码:
scrapy genspider -t crawl tencent tencent.com
它是Spider的派生类,Spider类的设计原则是只爬取start_url列表中的网页,而CrawlSpider类定义了一些规则(rule)来提供跟进link的方便的机制,从爬取的网页中获取link并继续爬取的工作更适合。
CrawlSpider继承于Spider类,除了继承过来的属性外(name、allow_domains),还提供了新的属性和方法
4.1 rules
CrawlSpider使用rules来决定爬虫的爬取规则,并将匹配后的url请求提交给引擎。所以在正常情况下,CrawlSpider不需要单独手动返回请求了。
在rules中包含一个或多个Rule对象,每个Rule对爬取网站的动作定义了某种特定操作,比如提取当前相应内容里的特定链接,是否对提取的链接跟进爬取,对提交的请求设置回调函数等。
如果多个rule匹配了相同的链接,则根据规则在本集合中被定义的顺序,第一个会被使用。
class scrapy.spiders.Rule(
link_extractor,
callback = None,
cb_kwargs = None,
follow = None,
process_links = None,
process_request = None
)
-
link_extractor
:是一个Link Extractor对象,用于定义需要提取的链接。 -
callback
: 从link_extractor中每获取到链接时,参数所指定的值作为回调函数,该回调函数接受一个response作为其第一个参数。注意:当编写爬虫规则时,避免使用parse作为回调函数。由于CrawlSpider使用parse方法来实现其逻辑,如果覆盖了 parse方法,crawl spider将会运行失败。
-
follow
:是一个布尔(boolean)值,指定了根据该规则从response提取的链接是否需要跟进。 如果callback为None,follow 默认设置为True ,否则默认为False。 -
process_links
:指定该spider中哪个的函数将会被调用,从link_extractor中获取到链接列表时将会调用该函数。该方法主要用来过滤。 -
process_request
:指定该spider中哪个的函数将会被调用, 该规则提取到每个request时都会调用该函数。 (用来过滤request)
4.2 LinkExtractors
class scrapy.linkextractors.LinkExtractor
Link Extractors 的目的很简单: 提取链接。
每个LinkExtractor有唯一的公共方法是 extract_links(),它接收一个 Response 对象,并返回一个 scrapy.link.Link 对象。
Link Extractors要实例化一次,并且 extract_links 方法会根据不同的 response 调用多次提取链接。
class scrapy.linkextractors.LinkExtractor(
allow = (),
deny = (),
allow_domains = (),
deny_domains = (),
deny_extensions = None,
restrict_xpaths = (),
tags = ('a','area'),
attrs = ('href'),
canonicalize = True,
unique = True,
process_value = None
)
主要参数:
allow
:满足括号中“正则表达式”的URL会被提取,如果为空,则全部匹配。deny
:满足括号中“正则表达式”的URL一定不提取(优先级高于allow)。allow_domains
:会被提取的链接的domains。deny_domains
:一定不会被提取链接的domains。restrict_xpaths
:使用xpath表达式,和allow共同作用过滤链接。
4.3 爬取规则(Crawling rules)
以腾讯招聘为例,给出配合rule使用CrawlSpider的例子:
-
首先运行
scrapy shell "http://hr.tencent.com/position.php?&start=0#a"
-
导入LinkExtractor,创建LinkExtractor实例对象。:
from scrapy.linkextractors import LinkExtractor page_lx = LinkExtractor(allow=('position.php?&start=\d+'))
allow : LinkExtractor对象最重要的参数之一,这是一个正则表达式,必须要匹配这个正则表达式(或正则表达式列表)的URL才会被提取,如果没有给出(或为空), 它会匹配所有的链接。
deny : 用法同allow,只不过与这个正则表达式匹配的URL不会被提取)。它的优先级高于 allow 的参数,如果没有给出(或None), 将不排除任何链接。
-
调用LinkExtractor实例的extract_links()方法查询匹配结果:
page_lx.extract_links(response)
-
没有查到:
[]
-
注意转义字符的问题,继续重新匹配:
page_lx = LinkExtractor(allow=('position\.php\?&start=\d+')) # page_lx = LinkExtractor(allow = ('start=\d+')) page_lx.extract_links(response)
4.4 CrawlSpider 版本
那么,scrapy shell测试完成之后,修改以下代码
#提取匹配 'http://hr.tencent.com/position.php?&start=\d+'的链接
page_lx = LinkExtractor(allow = ('start=\d+'))
rules = [
#提取匹配,并使用spider的parse方法进行分析;并跟进链接(没有callback意味着follow默认为True)
Rule(page_lx, callback = 'parse', follow = True)
]
这么写对吗?
不对!千万记住 callback 千万不能写 parse,再次强调:由于CrawlSpider使用parse方法来实现其逻辑,如果覆盖了 parse方法,crawl spider将会运行失败。
import scrapy
from scrapy.linkextractors import LinkExtractor
from scrapy.spiders import CrawlSpider, Rule
class TecentSpider(CrawlSpider):
name = 'tecent'
allowed_domains = ['hr.tencent.com']
start_urls = ['http://hr.tencent.com/position.php?&start=0']
page_lx = LinkExtractor(allow=r'start=\d+')
#position.php?&start=10#a
rules = (
Rule(page_lx, callback='parse_item', follow=True),
)
def parse_item(self, response):
items = response.xpath('//*[contains(@class,"odd") or contains(@class,"even")]')
for item in items:
temp = dict(
position=item.xpath("./td[1]/a/text()").extract()[0],
detailLink="http://hr.tencent.com/" + item.xpath("./td[1]/a/@href").extract()[0],
type=item.xpath('./td[2]/text()').extract()[0] if len(
item.xpath('./td[2]/text()').extract()) > 0 else None,
need_num=item.xpath('./td[3]/text()').extract()[0],
location=item.xpath('./td[4]/text()').extract()[0],
publish_time=item.xpath('./td[5]/text()').extract()[0]
)
print(temp)
yield temp
# parse() 方法不需要重写
# def parse(self, response):
# pass
运行: scrapy crawl tencent
5 Logging
Scrapy提供了log功能,可以通过 logging 模块使用。
可以修改配置文件settings.py,任意位置添加下面两行,效果会清爽很多。
LOG_FILE = "TencentSpider.log"
LOG_LEVEL = "INFO"
5.1 Log levels
- Scrapy提供5层logging级别:
CRITICAL
- 严重错误(critical)ERROR
- 一般错误(regular errors)WARNING
- 警告信息(warning messages)INFO
- 一般信息(informational messages)DEBUG
- 调试信息(debugging messages)
5.2 logging设置
通过在setting.py中进行以下设置可以被用来配置logging:
LOG_ENABLED
默认: True,启用loggingLOG_ENCODING
默认: 'utf-8',logging使用的编码LOG_FILE
默认: None,在当前目录里创建logging输出文件的文件名LOG_LEVEL
默认: 'DEBUG',log的最低级别LOG_STDOUT
默认: False 如果为 True,进程所有的标准输出(及错误)将会被重定向到log中。例如,执行 print "hello" ,其将会在Scrapy log中显示
6 中间件
6.1 分类和作用
-
分类
- 下载中间件
- 爬虫中间件
-
作用-预处理
request
和reponse
对象- 对
header
和cookie
进行更换处理 - 使用代理ip
- 对请求进行定制化操作
- 对
两种中间件都在middlewares.py
文件中
6.2 下载中间件(Downloader Middlewares)
下载中间件是处于引擎(crawler.engine)和下载器(crawler.engine.download())之间的一层组件,可以有多个下载中间件被加载运行。
- 当引擎传递请求给下载器的过程中,下载中间件可以对请求进行处理 (例如增加http header信息,增加proxy信息等);
- 在下载器完成http请求,传递响应给引擎的过程中, 下载中间件可以对响应进行处理(例如进行gzip的解压等)
要激活下载器中间件组件,将其加入到 DOWNLOADER_MIDDLEWARES 设置中。 该设置是一个字典(dict),键为中间件类的路径,值为其中间件的顺序(order)。
这里是一个例子:
DOWNLOADER_MIDDLEWARES = {
'mySpider.middlewares.MyDownloaderMiddleware': 543,
}
编写下载器中间件十分简单。每个中间件组件是一个定义了以下一个或多个方法的Python类:
class scrapy.contrib.downloadermiddleware.DownloaderMiddleware
6.2.1 process_request
process_request(self, request, spider)
当每个request通过下载中间件时,该方法被调用。
-
process_request() 必须返回以下其中之一:一个 None 、一个 Response 对象、一个 Request 对象或 raise IgnoreRequest:
-
返回 None值 :
Scrapy将继续处理该request,执行其他的中间件的相应方法,直到合适的下载器(download handler)被调用, 该request被执行(其response被下载。
-
返回 Response 对象:
Scrapy将不会调用 任何 其他的 process_request() 或 process_exception() 方法,或相应地下载函数; 其将返回该response给引擎。
-
返回 Request 对象:
Scrapy则停止调用 process_request方法并通过引擎重新返回给调度器。当新返回的request被执行后, 相应地中间件链将会根据下载的response被调用。
-
如果其raise一个 IgnoreRequest 异常,则安装的下载中间件的 process_exception() 方法会被调用。如果没有任何一个方法处理该异常, 则request的errback(Request.errback)方法会被调用。如果没有代码处理抛出的异常, 则该异常被忽略且不记录(不同于其他异常那样)。
-
-
参数:
request (Request 对象)
– 处理的requestspider (Spider 对象)
– 该request对应的spider
6.2.2 process_response
process_response(self, request, response, spider)
当下载器完成http请求,传递响应给引擎的时候调用
-
process_response() 必须返回以下其中之一: 返回一个 Response 对象、 返回一个 Request 对象或raise一个 IgnoreRequest 异常。
-
返回 Response 对象:
可以与传入的response相同,也可以是全新的对象, 该response会被在链中的其他中间件的 process_response() 方法处理。
-
返回 Request 对象:
中间件停止处理, 返回的request会通过引擎交给调度器,等待重新请求。处理类似于 process_request() 返回request所做的那样。
-
如果其抛出一个 IgnoreRequest 异常,则调用request的errback(Request.errback)。 如果没有代码处理抛出的异常,则该异常被忽略且不记录(不同于其他异常那样)。
-
-
参数:
request (Request 对象)
– response所对应的requestresponse (Response 对象)
– 被处理的responsespider (Spider 对象)
– response所对应的spider
6.3 使用案例
1. 创建middlewares.py
文件。
Scrapy代理IP、Uesr-Agent的切换都是通过DOWNLOADER_MIDDLEWARES
进行控制,我们在settings.py
同级目录下创建middlewares.py
文件,包装所有请求。
# middlewares.py
#!/usr/bin/env python
# -*- coding:utf-8 -*-
import random
import base64
from settings import USER_AGENTS
from settings import PROXIES
# 随机的User-Agent
class RandomUserAgent(object):
def process_request(self, request, spider):
useragent = random.choice(USER_AGENTS)
request.headers.setdefault("User-Agent", useragent)
class RandomProxy(object):
def process_request(self, request, spider):
proxy = random.choice(PROXIES)
if proxy['user_passwd'] is None:
# 没有代理账户验证的代理使用方式
request.meta['proxy'] = "http://" + proxy['ip_port']
else:
# 对账户密码进行base64编码转换
base64_userpasswd = base64.b64encode(proxy['user_passwd'])
# 对应到代理服务器的信令格式里
request.headers['Proxy-Authorization'] = 'Basic ' + base64_userpasswd
request.meta['proxy'] = "http://" + proxy['ip_port']
为什么HTTP代理要使用base64编码:
HTTP代理的原理很简单,就是通过HTTP协议与代理服务器建立连接,协议信令中包含要连接到的远程主机的IP和端口号,如果有需要身份验证的话还需要加上授权信息,服务器收到信令后首先进行身份验证,通过后便与远程主机建立连接,连接成功之后会返回给客户端200,表示验证通过,就这么简单,下面是具体的信令格式:
CONNECT 59.64.128.198:21 HTTP/1.1
Host: 59.64.128.198:21
Proxy-Authorization: Basic bGV2I1TU5OTIz
User-Agent: OpenFetion
其中
Proxy-Authorization
是身份验证信息,Basic后面的字符串是用户名和密码组合后进行base64编码的结果,也就是对username:password进行base64编码。
HTTP/1.0 200 Connection established
OK,客户端收到收面的信令后表示成功建立连接,接下来要发送给远程主机的数据就可以发送给代理服务器了,代理服务器建立连接后会在根据IP地址和端口号对应的连接放入缓存,收到信令后再根据IP地址和端口号从缓存中找到对应的连接,将数据通过该连接转发出去。
2. 修改settings.py配置USER_AGENTS和PROXIES
- 添加USER_AGENTS:
USER_AGENTS = [
"Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Win64; x64; Trident/5.0; .NET CLR 3.5.30729; .NET CLR 3.0.30729; .NET CLR 2.0.50727; Media Center PC 6.0)",
"Mozilla/5.0 (compatible; MSIE 8.0; Windows NT 6.0; Trident/4.0; WOW64; Trident/4.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; .NET CLR 1.0.3705; .NET CLR 1.1.4322)",
"Mozilla/4.0 (compatible; MSIE 7.0b; Windows NT 5.2; .NET CLR 1.1.4322; .NET CLR 2.0.50727; InfoPath.2; .NET CLR 3.0.04506.30)",
"Mozilla/5.0 (Windows; U; Windows NT 5.1; zh-CN) AppleWebKit/523.15 (KHTML, like Gecko, Safari/419.3) Arora/0.3 (Change: 287 c9dfb30)",
"Mozilla/5.0 (X11; U; Linux; en-US) AppleWebKit/527+ (KHTML, like Gecko, Safari/419.3) Arora/0.6",
"Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.8.1.2pre) Gecko/20070215 K-Ninja/2.1.1",
"Mozilla/5.0 (Windows; U; Windows NT 5.1; zh-CN; rv:1.9) Gecko/20080705 Firefox/3.0 Kapiko/3.0",
"Mozilla/5.0 (X11; Linux i686; U;) Gecko/20070322 Kazehakase/0.4.5"
]
-
添加代理IP设置PROXIES:
免费代理IP可以网上搜索,或者付费购买一批可用的私密代理IP:
PROXIES = [
{'ip_port': '111.8.60.9:8123', 'user_passwd': 'user1:pass1'},
{'ip_port': '101.71.27.120:80', 'user_passwd': 'user2:pass2'},
{'ip_port': '122.96.59.104:80', 'user_passwd': 'user3:pass3'},
{'ip_port': '122.224.249.122:8088', 'user_passwd': 'user4:pass4'},
]
- 除非特殊需要,禁用cookies,防止某些网站根据Cookie来封锁爬虫。
COOKIES_ENABLED = False
- 设置下载延迟
DOWNLOAD_DELAY = 3
- 最后设置setting.py里的DOWNLOADER_MIDDLEWARES,添加自己编写的下载中间件类。
DOWNLOADER_MIDDLEWARES = {
#'mySpider.middlewares.MyCustomDownloaderMiddleware': 543,
'mySpider.middlewares.RandomUserAgent': 1,
'mySpider.middlewares.ProxyMiddleware': 100
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· winform 绘制太阳,地球,月球 运作规律
· AI与.NET技术实操系列(五):向量存储与相似性搜索在 .NET 中的实现
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)