Scrapy 使用 CookieJar 解决站点怪异会话行为
那些将你的 UI 状态存储在自己的服务器的会话中的网站是难以导航的,更别说抓取。你有没有遇到过那些在同一个网站上打开的一个选项卡会影响其他选项卡的网站?那么,你可能会碰到这个问题。
虽然这是令人沮丧的,它甚至对于网络爬虫更糟糕。它会严重阻碍网络爬虫会话。不幸的是,这是 ASP.Net 和基于 J2EE 的网站的通用模式。而这正是 cookiejars 的用处所在。虽然不是经常需要 cookiejar,但是对于那些意想不到的情况,你会很高兴拥有它。
当你的爬取一个网站时,Scrapy 自动为你处理 cookie,存储并在随后的请求到将其发送到同一站点。但是,正如你可能知道的,Scrapy 请求是异步的。这意味着,你可能有发到相同的网站上的多个请求被同时处理,同时共享相同的 cookie。为避免在爬取这些类型的网站时,请求相互影响,你必须为不同的请求设置不同的 Cookie。
您可以通过使用一个 cookiejar 为同一网站中的不同页面存储单独的 cookie 来做到这点。该 cookiejar 只是在 Scrapy 爬取会话期间保持的一个 cookie 键值集合。你只需要为每个你想要存储的 cookie 定义一个唯一标识符,然后当你想要使用特定的 cookie 时,使用它的标识符。
例如,假设你想抓取一个网站上的多个类别,但这个网站存储与你在服务器会话中爬行/浏览的类别相关的数据。要同时爬取这些类别,则需要通过将类别名称作为 cookiejar 元参数的标识符来为每个类别创建一个 cookie:
class ExampleSpider(scrapy.Spider):
urls = [
'http://www.example.com/category/photo',
'http://www.example.com/category/videogames',
'http://www.example.com/category/tablets'
]
def start_requests(self):
for url in urls:
category = url.split('/')[-1]
yield scrapy.Request(url, meta={'cookiejar': category})
在此情况下,将管理三种不同的 Cookie(photo、videogames 和 tablets)。每当你传递一个不存在的键作为 cookiejar 元值(例如,当一个类别名称尚未访问)时,你可以创建一个新的 Cookie。当我们传递的键已经存在时, Scrapy 使用该请求相应的 cookie。
所以,例如,如果你想重新使用已被用来抓取 videogames 页面的 cookie,那么你只需要将 videogames 作为唯一键传递给 cookiejar。它将使用先用的 cookie,而不是创建一个新的 cookie:
yield scrapy.Request('http://www.example.com/atari2600', meta={'cookiejar': 'videogames'})
添加备用的 CSS/XPath 规则
当你需要完成比简单地填充字典或带有你的 spider 收集的数据的 Item 对象更多的东西时, Item Loader 是有用的。例如,你可能需要将一些后处理逻辑添加到你刚刚收集的数据中。你可能对某些如将标题中的每个单词首字母大写一样简单的事,甚至是更复杂的操作有兴趣。使用 ItemLoader,你可以从 spider 中解耦这种后处理逻辑,以便拥有一个更易于维护的设计。
这个技巧说明如何将额外的功能添加到一个 Item Loader 中。比方说,你正爬取 Amazon.com,并且提取每个产品的价格。你可以使用 Item Loader 来为 ProductItem 对象填充产品数据:
class ProductItem(scrapy.Item):
name = scrapy.Field()
url = scrapy.Field()
price = scrapy.Field()
class AmazonSpider(scrapy.Spider):
name = "amazon"
allowed_domains = ["amazon.com"]
def start_requests(self):
...
def parse_product(self, response):
loader = ItemLoader(item=ProductItem(), response=response)
loader.add_css('price', '#priceblock_ourprice ::text')
loader.add_css('name', '#productTitle ::text')
loader.add_value('url', response.url)
yield loader.load_item()
这种方法工作得很好,除非被爬取的产品是一次交易。这是因为对比那些普通的价格,Amazon 以一种稍微不同的格式展示交易价格。而普通产品的价格是这样表示的:
<span id="priceblock_ourprice" class="a-size-medium a-color-price">
$699.99
</span>
交易价格显示稍微有点不同:
<span id="priceblock_dealprice" class="a-size-medium a-color-price">
$649.99
</span>
要处理这种情况的一个好方法是,为 Item loader 中的价格字段添加一个后备规则。这是一个只有当该字段的前一规则已经失败时才应用的规则。要用 Item Loader 做到这一点,你可以添加一个 add_fallback_css
方法:
class AmazonItemLoader(ItemLoader):
default_output_processor = TakeFirst()
def get_collected_values(self, field_name):
return (self._values[field_name]
if field_name in self._values
else self._values.default_factory())
def add_fallback_css(self, field_name, css, *processors, **kw):
if not any(self.get_collected_values(field_name)):
self.add_css(field_name, css, *processors, **kw)
正如你所看到的, 如果对于该字段,没有之前收集到的值,那么 add_fallback_css
方法将使用 CSS 规则。现在,我们可以改变我们的 spider 来使用 AmazonItemLoader,然后添加后备 CSS 规则到我们的 loader 中:
def parse_product(self, response):
loader = AmazonItemLoader(item=ProductItem(), response=response)
loader.add_css('price', '#priceblock_ourprice ::text')
loader.add_fallback_css('price', '#priceblock_dealprice ::text')
loader.add_css('name', '#productTitle ::text')
loader.add_value('url', response.url)
yield loader.load_item()
这个技巧可以节省你的时间,让你的 spider 更健壮。如果有一个 CSS 规则无法获取数据,那么可以应用其他跪在来提取所需的数据。如果 Item Loader 对于你来说是新玩意,那么 看看这个文档 。
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论