使用scrapy按字段订购json

时间:2018-02-16 13:19:03

标签: python json web-scraping scrapy scrapy-spider

我已经创建了一个蜘蛛来从projecteuler.net中解决问题。 Here我已经用

结束了我对相关问题的回答
  

我使用命令scrapy crawl euler -o euler.json启动它,它输出一个无序的json对象数组,每个人都对一个问题相应:这对我来说很好,因为我将用javascript处理它,即使我认为通过scrapy解决订购问题也非常简单。

但不幸的是,通过scrapy订购要在json中写入的项目(我需要通过id字段升序)似乎并不那么简单。我研究过每一个组件(中间件,管道,出口商,信号等......),但似乎没有人能用于此目的。我得出的结论是,在scrapy中根本不存在解决这个问题的解决方案(除了,可能是一个非常详细的技巧),你被迫在第二阶段订购。你同意吗,或者你有什么想法吗?我在这里复制刮刀的代码。

蜘蛛:

# -*- coding: utf-8 -*-
import scrapy
from eulerscraper.items import Problem
from scrapy.loader import ItemLoader


class EulerSpider(scrapy.Spider):
    name = 'euler'
    allowed_domains = ['projecteuler.net']
    start_urls = ["https://projecteuler.net/archives"]

    def parse(self, response):
        numpag = response.css("div.pagination a[href]::text").extract()
        maxpag = int(numpag[len(numpag) - 1])

        for href in response.css("table#problems_table a::attr(href)").extract():
            next_page = "https://projecteuler.net/" + href
            yield response.follow(next_page, self.parse_problems)

        for i in range(2, maxpag + 1):
            next_page = "https://projecteuler.net/archives;page=" + str(i)
            yield response.follow(next_page, self.parse_next)

        return [scrapy.Request("https://projecteuler.net/archives", self.parse)]

    def parse_next(self, response):
        for href in response.css("table#problems_table a::attr(href)").extract():
            next_page = "https://projecteuler.net/" + href
            yield response.follow(next_page, self.parse_problems)

    def parse_problems(self, response):
        l = ItemLoader(item=Problem(), response=response)
        l.add_css("title", "h2")
        l.add_css("id", "#problem_info")
        l.add_css("content", ".problem_content")

        yield l.load_item()

档案:

import re

import scrapy
from scrapy.loader.processors import MapCompose, Compose
from w3lib.html import remove_tags


def extract_first_number(text):
    i = re.search('\d+', text)
    return int(text[i.start():i.end()])


def array_to_value(element):
    return element[0]


class Problem(scrapy.Item):
    id = scrapy.Field(
        input_processor=MapCompose(remove_tags, extract_first_number),
        output_processor=Compose(array_to_value)
    )
    title = scrapy.Field(input_processor=MapCompose(remove_tags))
    content = scrapy.Field()

2 个答案:

答案 0 :(得分:2)

如果我需要对输出文件进行排序(我假设你有正当理由想要这个),我可能会写一个自定义exporter

This是Scrapy内置JsonItemExporter的实现方式 通过一些简单的更改,您可以修改它以将项目添加到export_item()中的列表中,然后对项目进行排序并在finish_exporting()中写出文件。

由于你只是抓了几百个项目,所以存储它们列表而不是写入文件直到抓取完成的缺点对你来说应该不是问题。

答案 1 :(得分:0)

到目前为止,我已经找到了一个使用管道的工作解决方案:

import json

class JsonWriterPipeline(object):

    def open_spider(self, spider):
        self.list_items = []
        self.file = open('euler.json', 'w')

    def close_spider(self, spider):
        ordered_list = [None for i in range(len(self.list_items))]

        self.file.write("[\n")

        for i in self.list_items:
            ordered_list[int(i['id']-1)] = json.dumps(dict(i))

        for i in ordered_list:
            self.file.write(str(i)+",\n")

        self.file.write("]\n")
        self.file.close()

    def process_item(self, item, spider):
        self.list_items.append(item)
        return item

虽然它可能不是最佳的,因为指南在另一个例子中暗示:

  

JsonWriterPipeline的目的只是介绍如何编写项目管道。如果您确实要将所有已删除的项目存储到JSON文件中,则应使用Feed导出。