He creado una araña para eliminar problemas de projecteuler.net. Aquí He concluido mi respuesta a una pregunta relacionada con

Lanzo esto con el comando scrapy crawl euler -o euler.json y genera una matriz de objetos json desordenados, todos responden a un solo problema: esto está bien para mí porque lo voy a procesar con javascript, incluso si Piense que resolver el problema de pedidos a través de Scrapy puede ser muy simple.

Pero desafortunadamente, ordenar elementos para escribir en json por scrapy (necesito un orden ascendente por campo de identificación) no parece ser tan simple. He estudiado todos los componentes (middlewares, tuberías, exportadores, señales, etc.) pero nadie parece útil para este propósito. Llegué a la conclusión de que no existe una solución para resolver este problema en absoluto (excepto, tal vez, un truco muy elaborado), y se ve obligado a ordenar las cosas en una segunda fase. ¿Estás de acuerdo o tienes alguna idea? Copio aquí el código de mi raspador.

Araña:

# -*- 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()

Articulo:

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()
0
Lore 16 feb. 2018 a las 16:19

2 respuestas

La mejor respuesta

Si necesitara ordenar mi archivo de salida (supongo que tiene una razón válida para querer esto), probablemente escribiría un exportador.

Así se integra Scrapy {{X0 }} está implementado.
Con unos pocos cambios simples, puede modificarlo para agregar los elementos a una lista en export_item(), y luego ordenar los elementos y escribir el archivo en finish_exporting().

Dado que solo está raspando unos pocos cientos de elementos, las desventajas de almacenar una lista de ellos y no escribir en un archivo hasta que finalice el rastreo no deberían ser un problema para usted.

2
stranac 16 feb. 2018 a las 14:28

Por ahora he encontrado una solución de trabajo usando pipeline:

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

Aunque puede no ser óptimo, porque la guía sugiere en otro ejemplo:

El propósito de JsonWriterPipeline es simplemente presentar cómo escribir tuberías de elementos. Si realmente desea almacenar todos los elementos raspados en un archivo JSON, debe usar las exportaciones de Feed.

0
Lore 20 feb. 2018 a las 15:14