我想做的是刮刮多页并在 单个数组 中产生结果。
我发现this post,它描述了如何抓取多个页面并从每个被抓取的页面中产生文本。
我提到了这种方法(并对其做了一些修改),这就是我的蜘蛛的样子……
from scrapy import Request
from test_project.items import PriceSpiderItem
class RoomsSpider(scrapy.Spider):
name = 'rooms'
allowed_domains = ['sample.com']
start_urls = ['http://sample.com/rooms']
def parse(self, response):
for resource in response.xpath('.//*[@class="sample"]'):
item = PriceSpiderItem()
item['result'] = resource.xpath("text()").extract_first()
yield item
nextUrl = response.xpath('//*[@label="Next"]/@href').extract_first()
if(nextUrl is not None):
absoluteNextUrl = response.urljoin(nextUrl)
yield Request(url=absoluteNextUrl, callback=self.parse)
但是,采用这种方法,结果将看起来像...
{
"items" : [
{"result": "blah blah"},
{"result": "blah blah blah blah blah"},
{"result": "blah blah blah blah"},
...
etc.
...
{"result": "blah blah blah blah blah"},
{"result": "blah blah blah"}
]
}
这并非我要达到的目标。理想情况下,结果将放在单个数组中,例如...
{
"items" : [
"blah blah",
"blah blah blah blah blah",
"blah blah blah blah",
...
"blah blah blah blah blah",
"blah blah blah"
]
}
但是,我不确定它是否可以实现。
据我了解,Scrapy
是非阻塞的,因此我可能能够将结果存储在全局变量中,并在Spider抓取所有页面之后产生它。
(也就是说,我不想使用全局变量,因为随着应用程序的增大,可能难以维护它)
任何建议将不胜感激。
PS
@Wim Hermans给了我有趣的方法(谢谢!)。
其中,可以将结果存储在具有ItemPipeline的文件中,并在所有页面都经过爬网后产生。
这似乎很有希望,但是如果Spider在scrapyrt(或类似的东西)上运行以用作REST API端点,则我不确定如何处理并发问题。
# 1. Client A makes a request
# 2. Spider receives Client A's request
# 3. Client B makes a request
# 4. Spider receives Client B's request
# 5. Spider fulfills Client B's request, saves the result in "result.csv"
# 6. Spider fulfills Client A's request, updates "result.csv" with Client A's request
# 7. Spider responses with "result.csv" for bot Client A and B
Scrapy是非阻塞的,所以我想这样的情况可能发生
P.P.S。
如果必须yield
,@ Wim Hermans提出的第一个解决方案可能是最好的解决方案(但请注意内存使用情况)
答案 0 :(得分:1)
我可以想到几种不同的选择:
def parse(self, response):
result = response.meta.get('result', [])
for resource in response.xpath('.//*[@class="sample"]'):
result.append(resource.xpath("text()").extract_first())
nextUrl = response.xpath('//*[@label="Next"]/@href').extract_first()
meta = {'result': result}
if nextUrl:
absoluteNextUrl = response.urljoin(nextUrl)
yield Request(url=absoluteNextUrl, callback=self.parse, meta=meta)
else:
item = PriceSpiderItem()
item['result'] = result
yield item
根据要获取的数据量,这可能会变得很繁重。
您不会在meta中传递完整的结果集,而是编写了一个项目管道,该结果管道将结果保存在列表中并在最后给出结果。
class CombineResultsPipeline(object):
def __init__(self):
self.results = []
def process_item(self, item, spider):
self.results.append(item['result'])
return item
def close_spider(self, spider):
print(f"full result set is {self.results}")
这基本上就像将结果存储在全局变量中一样,因此可能也不完全是您所需要的。
一种更节省内存的方法是将结果写入文件(或数据库),然后对其进行一些处理以获取所需格式的结果。您可以在项目管道(items to json)中执行此操作,也可以仅使用Feed导出(feed exports)。