我正在构建一个 python scraper,目前它采用多个 URL 并提取有关汽车广告的信息。代码如下所示:
import time
import csv
import scrapy
import json
from scrapy.loader import ItemLoader
from urllib.parse import urlencode, quote
class autovitSpider(scrapy.Spider):
name = "autovit"
start_urls = [
url1,
url2,
url3,
url4,
url5,
]
def parse(self, response):
for list_ in response.css("div.offer-params"):
for sublist in list_.css("ul.offer-params__list"):
for elem in sublist.css("li.offer-params__item"):
if elem.css("span.offer-params__label::text").get() == "Anul" or elem.css("span.offer-params__label::text").get() == "Km" or elem.css("span.offer-params__label::text").get() == "Putere" or elem.css("span.offer-params__label::text").get() == "Capacitate cilindrica":
yield {
"type":elem.css("span.offer-params__label::text").get(),
"value":elem.css("div.offer-params__value::text").get().replace("\n ","").replace(" ","").replace(" ",""),
}
elif elem.css("span.offer-params__label::text").get() == "Marca" or elem.css("span.offer-params__label::text").get() == "Model" or elem.css("span.offer-params__label::text").get() == "Versiune" or elem.css("span.offer-params__label::text").get() == "Combustibil" or elem.css("span.offer-params__label::text").get() == "Culoare" or elem.css("span.offer-params__label::text").get() == "Cutie de viteze" or elem.css("span.offer-params__label::text").get() == "Numar de portiere":
yield {
"type":elem.css("span.offer-params__label::text").get(),
"value":elem.css("a.offer-params__link::text").get().replace("\n ","").replace(" ",""),
}
else:
pass
yield {"url": response.request.url,
"price": response.css("span.offer-price__number::text").get().replace(" ",""),
}
我知道
yield
很丑,但这就是我正在抓取的网站。汽车的不同属性有不同的 html div/a's/etc.
无论如何,我注意到以下问题:
对于多个链接,蜘蛛有时(有些运行按正确的顺序返回数据,有些则不然)开始弄乱其生成的值的顺序。我特别需要这个顺序,并且每个广告都需要 10 行。有时它会将多个广告的信息混在一起。我认为这是由异步请求引起的,因为蜘蛛加载多个 URL,(几乎)同时开始抓取它们,并在发现感兴趣的内容时生成信息。 现在,我通过设置
CONCURRENT_REQUESTS = 1
解决了这个问题,但这(显然)让它变得更慢。有什么办法可以解决这个问题,同时又不限制蜘蛛一次只能发出1个请求吗?比如为它抓取的每个 URL 生成一个单独的 dict
,或者跟踪 dict
中每个元素的来源?我知道我可以通过为字典中的每个条目创建一个基于 URL 的键,然后将具有相同键的项目聚集在一起来解决这个问题。但同样,这会增加与排序相关的额外时间并降低效率。
请记住,我对这些东西如何工作的技术方面了解不多。我从概念上理解异步请求或
yield
命令如何工作,但我不知道其背后的实际机制。我什至不知道 Scrapy 本身是如何工作的(创建所有这些文件,从 shell 处理它们等),因为我习惯于编写单个文件程序来执行它们内部的操作,而无需在它们背后有一个完整的运行过程。因此,过于技术性的答案可能没有帮助。
为了参考我的理解水平,我尝试在
for url in url_list:
之外创建一个 class autovitSpider
循环,该循环遍历 url 列表并为每个单独的 url 重新运行蜘蛛。它不起作用,并且仅生成第一个网址的数据。还尝试在 class autovitSpider
内循环并为 start_urls
提供单独的 url。与第一种情况相同的结果。
编辑:当使用多个并发请求时,也会出现某种数据丢失:
INFO: Stored json feed (41 items) in: a.json
INFO: Stored json feed (31 items) in: a.json
连续运行,没有更改代码。
现在,我通过设置 CONCURRENT_REQUESTS = 1 解决了这个问题
我知道我可以通过为字典中的每个条目创建一个基于 URL 的键,然后将具有相同键的项目聚集在一起来解决这个问题。但同样,这会增加与排序相关的额外时间并降低效率。
您尝试过对此进行基准测试吗?
我认为将并发请求设置为 1 比在字典中对数据进行排序会损失更多的处理时间。将并发请求设置为 1 基本上可以防止您从并行化的效果中受益,同时增加与并行化相关的所有问题。另一方面,字典的内存很大,除非你在烤面包机上运行或者你正在废弃一些非常大的东西,否则应该没问题。最坏的情况是,您可以将结果转储到数据库中。
但是,由于您正在生成异步对象,如果您没有在某种键上匹配它们,您将无法对它们进行正确排序。这就是异步操作的本质:你不知道它什么时候结束。
因此,简而言之:使用字典,您不会损失太多时间或内存,但会使用更多并发请求。
当使用多个并发请求时,也会出现某种数据丢失
大多数服务器都针对大量异步请求提供保护。
我不太了解你的问题,因为我缺少诸如你的用户代理或你正在废弃的系统之类的信息,但 scrapy 的文档说“Scrapy/VERSION (+https://scrapy.org)”是默认值用户代理,因此服务器会将您识别为机器人,并可能在某个时候限制请求。
如果您的请求最终失败(因为您受到限制),那么您可能不会获得所有结果。我在你的函数中没有看到 try- except ,所以基本上如果你失败了,你应该收到一个错误,但由于你的操作都是并行的,它会继续执行其他操作。
假设你有操作A-B-C,A=成功=产生你的东西,B=失败=没有产生,C=成功=再次产生。因此,如果您第一次受到限制,一切都成功,因此 json 有 3 个条目,下次您遇到错误(因为服务器限制了您),因此 json 有 2 个条目。
您应该尝试人为地减慢请求速度以满足服务器的要求。如果您正在将网站视为文档,请使用它来限制自己。否则就是反复试验。
我希望这会有所帮助。