meta的深浅拷贝
当我们获取数据的时候,会创建⼀个存储对象(例如item)。如果不⽤涉及到传参,那么这个存储对象放置的位置是没有讲究的。
普通情况
无论是放置在循环中或者循环外,使用以下两种方法都是可以的:
for i in datas:
data = {'data': i}
yield data
data = {}
for i in datas:
data['data'] = i
yield data
复杂情况
当我们的的存储对象需要从⼀个请求传递到另外⼀个请求时,这种请求就复杂得多,以上两种办法可能要进⾏取舍。
情况1
for i in datas:
data = {'data': i}
yield scrapy.Request(url=detail_url,
callback=self.detail_parse,
meta={'item': data})
for i in datas:
data = TencentItem()
data['data']= i
yield scrapy.Request(url=detail_url,
callback=self.detail_parse,
meta={'item': data})
当使⽤这种⽅式的时候,传递到另外⼀个请求是没有问题的,因为每次存储的存储对象,都是创建出来的新对象,对象之间不会影响到,所以不会影响传输的数据。
情况2
data = {}
for i in datas:
data['data'] = i
yield scrapy.Request(url=detail_url,
callback=self.detail_parse,
meta={'item': data})
data = TencentItem()
for i in datas:
data['data'] = i
yield scrapy.Request(url=detail_url,
callback=self.detail_parse,
meta={'item': data})
当使⽤上⾯这种情况时,数据就会出现问题,因为每次使⽤的数据是同⼀个对象,那么在新的详情解析异步代码中,进⾏参数赋值会影响到其他的详情解析,归根到底是使⽤了同⼀个实例对象。
解决办法:
解决办法就是将实例化的对象拷⻉⼀份,拷⻉的⽅法有深浅两种拷⻉,分别是copy模块中的copy和deepcopy。
copy浅拷贝
copy是浅拷贝,它是复制一个对象指向原本的数据,同时会拷贝一份数据的表层数据。内层数据是放在同一个内存地址的,所以当修改内存数据的时候,原数据和拷贝的数据都会发生改变。
from copy import copy
data = [1, [2, 3, 4]]
copy_data = copy(data)
copy_data[0] = 2
copy_data[1][0] = 1
print(data)
print(copy_data)
结果:
从以上结果可以看出,当外层数据被修改的时候,不会影响到原本的数据,当修改里面内部数据的时候就会影响到原本数据,这是因为copy浅拷贝,只会拷贝最外层的数据。
总结: 对于列表,字典这种可变的数据类型,拷⻉出来的对象修改数据依然会影响到原本的数据,因为他们 指定的数据是同⼀个数据,所以修改和获取也是同⼀个数据。
deepcopy深拷贝
deepcopy拷⻉会将深层的数据也进⾏拷⻉,这样拷⻉会将数据完全的复制⼀份出来,这样就不会影响到原本的数据了。
from copy import deepcopy
data = [1, [2, 3, 4]]
copy_data = deepcopy(data)
copy_data[0] = 2
copy_data[1][0] = 1
print(data)
print(copy_data)
结果:
总结:通过以上结果看出,深拷贝出来的数据和原数据是两份一样的数据,并且存储的地址也不一样,所以对拷贝数据进行修改的时候,对原数据是没有任何影响的。
综上所述,得出解决问题的办法:
from copy import deepcopy
data = TencentItem()
for i in datas:
data['data']= i
yield scrapy.Request(url=detail_url,
callback=self.detail_parse,
meta={'item': deepcopy(data)})
Scrapy中间件
下载中间件,可以处理请求之前和请求之后的数据。 下载中间件存在在middlewares.py⽂件中DownloaderMiddleware类如果想要使⽤,需要在setting.py中启动下载中间件。运⾏
DOWNLOADER_MIDDLEWARES = {
"xxx.middlewares.TencentDownloaderMiddleware": 543,
}
middlewares.py的中间件类:
@classmethod
def from_crawler(cls, crawler):
# This method is used by Scrapy to create your spiders.
s = cls()
crawler.signals.connect(s.spider_opened, signal=signals.spider_opened)
return s
这个代码是⼀个类⽅法,⽤于整个爬⾍中间件的创建。
如果存在,则调⽤from_crawler类⽅法创建中间件实例。内部代码通过crawler.signals.connect ⽅法连接了 spider_opened 信号和 s.spider_opened ⽅法,这样在 spider_opened 信号触发时就会执⾏ s.spider_opened ⽅法,也就是爬⾍运⾏中间件的⽅法会依次执⾏中间件的 spider_opened ⽅法。
def spider_opened(self, spider):
spider.logger.info("Spider opened: %s" % spider.name)
spider_opened 被scrapy的信号关联,当爬⾍启动的时候,⾃动调⽤中间件的spider_opened⽅法。此⽅法中使⽤了⽇志的输出,输出 "Spider opened: %s" % spider.name 爬⾍的名称,表⽰爬⾍已经启动。
既然他是初始化调⽤的地⽅,那么它通常⽤于在爬⾍被打开时执⾏⼀些初始化操作,必要的准备⼯作,或者记录⼀些相关的⽇志信息。
def process_request(self, request, spider):
# Called for each request that goes through the downloader
# middleware.
# Must either:
# - return None: continue processing this request
# - or return a Response object
# - or return a Request object
# - or raise IgnoreRequest: process_exception() methods of
# installed downloader middleware will be called
return None
#
def process_request(self, request, spider):
# 对于通过下载的每个请求,都会调⽤此⽅法
# 必须知道的:
# - return None:继续处理此请求
# - 或者返回⼀个 Response 对象
# - 或者返回⼀个 Request 对象
# - 或者 raise IgnoreRequest: process_exception() ⽅法
return None
对于通过下载的每个请求,都会调⽤此⽅法。
1. 返回 None :继续处理该请求,将它传递给后续的下载中间件或下载器进⾏处理。
2. 返回 Response 对象:表⽰该请求已被成功处理,并返回⼀个包含响应数据的 Response对象。这样,后续的下载中间件和爬⾍就会跳过该请求,直接获取响应数据进⾏处理。
3. 返回 Request 对象:表⽰该请求需要重新发送⼀个新的请求。这个新的请求将会被发送到下载
器,并经过后续的下载中间件的处理。这在需要重定向、重新发送请求、实现请求的动态⽣成等情况下使⽤⽤。
4. 抛出 IgnoreRequest 异常:表⽰该请求应该被忽略,不再进⾏后续的处理。下载中间件的process_exception ⽅法将会被调⽤来处理这个异常。
process_request ⽅法通常⽤于对将要发送的请求进⾏预处理或拦截,你可以对请求进⾏各种操作,例如修改请求的 headers、添加请求参数、对请求进⾏过滤或验证等。然后根据处理结果选择返回不同的对象或继续处理该请求。
def process_exception(self, request, exception, spider):
# Called when a download handler or a process_request()
# (from other downloader middleware) raises an exception.
# Must either:
# - return None: continue processing this exception
# - return a Response object: stops process_exception() chain
# - return a Request object: stops process_exception() chain
pass
在请求过程中出现错误会⾃动调⽤该⽅法。
• 返回 None:表⽰继续处理该异常,可以继续执⾏后续的下载中间件的 process_exception⽅法。
• 返回 Response 对象:表⽰停⽌异常处理链,将会直接交给爬⾍进⾏处理,相当于正常情况下的返回 Response 对象。
• 返回 Request 对象:表⽰停⽌异常处理链,并重新发送⼀个新的请求。
process_exception 这个⽅法通常被⽤于处理下载过程中出现的异常,⽐如⽹络连接错误、超时、服务器错误等。通过在 process_exception ⽅法中进⾏处理,你可以根据异常的具体情况来决定如何处理异常,也可以动态地修改请求、重新发送请求等。
def process_response(self, request, response, spider):
# Called with the response returned from the downloader.
# Must either;
# - return a Response object
# - return a Request object
# - or raise IgnoreRequest
return response
• 返回⼀个 Response 对象:表⽰对该响应进⾏处理后返回⼀个新的响应。这样,后续的下载中间件和爬⾍就会使⽤这个新的响应进⾏后续处理。
• 返回⼀个 Request 对象:表⽰对该响应进⾏处理后返回⼀个新的请求对象。这个新的请求对象将会被发送到下载器,并经过后续的下载中间件的处理。
• 抛出 IgnoreRequest 异常:表⽰该响应应该被忽略,不再进⾏后续的处理。
这个⽅法的作⽤是对下载得到的响应进⾏处理。你可以选择返回⼀个新的 Response 对象,返回⼀个新的 Request 对象,或者抛出 IgnoreRequest 异常来终⽌该响应的处理。
总结:
- process_request(self, request, spider) : 这个⽅法会在每个请求发送之前被调⽤,⽤于对请求进⾏预处理或拦截。你可以在这个⽅法中修改请求的 headers、添加请求参数、对请求进⾏过滤或验证等。根据处理结果,可以返回 None 继续处理该请求,返回⼀个 Response 对象表⽰请求已被处理完毕,返回⼀个新的 Request 对象表⽰需要重新发送请求,或者抛出
IgnoreRequest 异常表⽰忽略该请求。
-
process_response(self, request, response, spider) : 这个⽅法会在下载器返回响应后被调⽤,⽤于对响应进⾏处理。你可以在这个⽅法中对响应进⾏改动或过滤,并返回⼀个新的 Response 对象⽤于后续处理,或返回⼀个新的 Request 对象表⽰需要重新发送请求,或抛出IgnoreRequest 异常来忽略该响应。
-
process_exception(self, request, exception, spider) : 这个⽅法会在下载过程中出现异常时被调⽤,⽤于处理下载过程中的异常情况。你可以在这个⽅法中根据异常的具体情况进⾏处理,例如重新发送请求、修改请求参数等。可以选择返回 None 继续处理该异常,返回⼀Response 对象⽤于后续处理,返回⼀个新的 Request 对象表⽰需要重新发送请求,或抛出IgnoreRequest 异常来忽略该异常。
演示
class CustomMiddleware:
def process_request(self, request, spider):
# 在请求头中添加⾃定义信息
request.headers['User-Agent'] = 'MyCustomUserAgent'
request.meta['custom_info'] = 'SomeCustomInfo'
# # 可以修改请求的URL、Headers、添加代理等
# 继续处理该请求
return None
def process_response(self, request, response, spider):
# 对响应进⾏加⼯处理
modified_body = response.body.upper() # 将响应数据转换为⼤写
return response.replace(body=modified_body)
def process_exception(self, request, exception, spider):
# 处理异常的⽅法
pass
常用使用示例
User-Agent中间件:用于切换随机的User-Agent
import random
from fake_useragent import UserAgent
class UADownloaderMiddleware:
def process_request(self, request, spider):
ua = UserAgent()
user_agent = ua.random
request.headers['User-Agent'] = user_agent
from scrapy import signals
import random
class RandomUserAgentMiddleware:
def __init__(self, user_agents):
self.user_agents = user_agents
@classmethod
def from_crawler(cls, crawler):
user_agents = crawler.settings.get('USER_AGENTS')
return cls(user_agents)
def process_request(self, request, spider):
user_agent = random.choice(self.user_agents)
request.headers['User-Agent'] = user_agent
RandomUserAgentMiddleware 类会在每个请求发出前,从配置的 User-Agent 列表中随机选择⼀个 User-Agent,并将其设置到请求头中。当然也可以写简单点
import random
class WzryDownloaderMiddleware:
def process_request(self, request, spider):
# UA池
user_agent_list = [
'Mozilla/5.0 (X11; U; Linux i686; fr; rv:1.8.1.8) Gecko/20071030Fedora/2.0.0.8-2.fc8 Firefox/2.0.0.8',
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36(KHTML, like Gecko) Chrome/42.0.2311.135 Safari/537.36 Edge/12.246',
'Mozilla/5.0 (Windows; U; MSIE 9.0; WIndows NT 9.0; en-US))',
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36(KHTML like Gecko) Chrome/46.0.2486.0 Safari/537.36 Edge/13.10586',
'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/534.24 (KHTML, likeGecko) Chrome/11.0.696.3 Safari/534.24',
'Mozilla/5.0 (Macintosh; U; PPC Mac OS X; en-us) AppleWebKit/312.1(KHTML, like Gecko)',
'Opera/9.21 (X11; Linux i686; U; en)',
'Mozilla/5.0 (X11; U; Linux i686; de-DE; rv:1.7.5) Gecko/20041108Firefox/1.0',
'Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9pre)Gecko/2008040318 Firefox/3.0pre (Swiftfox)',
'Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; WOW64;Trident/5.0; chromeframe/12.0.742.112)',
'Opera/9.80 (X11; Linux x86_64; U; en) Presto/2.2.15Version/10.00',
'Mozilla/5.0 (Windows NT 10.0; WOW64; rv:45.66.18) Gecko/20177177 Firefox/45.66.18',
'Mozilla/2.0 (compatible; MSIE 3.01; Windows 95)',
'Opera/9.80 (Windows NT 5.1; U; cs) Presto/2.2.15 Version/10.10',
'Mozilla/5.0 (Windows NT 6.0) AppleWebKit/535.2 (KHTML, likeGecko) Chrome/15.0.874.120 Safari/535.2',
'Mozilla/4.0 (compatible; MSIE 5.0; Windows 98; DigExt; YComp5.0.2.6; yplus 1.0)',
'Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; es-la) Opera9.27',
'Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_3; de-de)AppleWebKit/531.22.7 (KHTML, like Gecko) Version/4.0.5 Safari/531.22.7',
'Mozilla/5.0 (Windows NT 10.0) AppleWebKit/537.36 (KHTML, likeGecko) Chrome/86.0.8810.3391 Safari/537.36 Edge/18.14383',
'Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.8.0.8)Gecko/20061107 Fedora/1.5.0.8-1.fc6 Firefox/1.5.0.8']
request.headers['User-Agent'] = random.choice(user_agent_list)
print('现在使⽤的UA是:', request.headers['User-Agent'])
return None
IP代理中间件:用于切换随机的IP代理
import random
class RandomProxyMiddleware:
def __init__(self, proxies):
self.proxies = proxies
@classmethod
def from_crawler(cls, crawler):
proxies = crawler.settings.get('PROXIES')
return cls(proxies)
def process_request(self, request, spider):
# request.meta['proxy'] = 'https://61.64.144.123:8080' 代理ip
proxy = random.choice(self.proxies)
request.meta['proxy'] = proxy
Selenium代替下载器:用于使用selenium进行请求和处理javascript渲染的页面。
from scrapy.http import HtmlResponse
from selenium import webdriver
class SeleniumMiddleware:
def __init__(self):
self.driver = webdriver.Chrome()
def process_request(self, request, spider):
self.driver.get(request.url)
body = self.driver.page_source.encode('utf-8')
return HtmlResponse(url=request.url, body=body, encoding='utf-8',
request=request, status=200)
请求失败切换使用的代理ip
import random
def process_exception(self, request, exception, spider):
# 检查请求的 URL 协议,如果是 HTTP,则设置代理为随机选择的 http 代理
if request.url.split(':')[0] == 'http':
request.meta['proxy'] = 'http://' + random.choice(self.PROXY_http)
# 如果是 HTTPS,则设置代理为随机选择的 https 代理
if request.url.split(':')[0] == 'https':
request.meta['proxy'] = 'https://' + random.choice(self.PROXY_https)
# 返回新的请求对象,以便重新发送请求
return request