Заказать JSON по полю, используя скрап
Я создал паука, чтобы очистить проблемы от projecteuler.net. Здесь я завершил свой ответ на связанный вопрос с
Я запускаю это с помощью команды scrapy crawl euler -o euler.json, и он выводит массив неупорядоченных объектов json, каждый из которых соответствует одной проблеме: это хорошо для меня, потому что я собираюсь обработать его с помощью javascript, даже если я думаю, что решение проблемы заказа с помощью скрапа может быть очень простым.
Но, к сожалению, упорядочить элементы для записи в json с помощью scrapy (мне нужен возрастающий порядок по полю id) не так просто. Я изучил каждый компонент (связующее ПО, конвейеры, экспортеры, сигналы и т. Д.), Но никто не кажется полезным для этой цели. Я пришел к выводу, что решения для решения этой проблемы вообще не существует в скрапе (за исключением, может быть, очень сложного трюка), и вы вынуждены заказывать вещи на втором этапе. Вы согласны или у вас есть идея? Я копирую здесь код моего скребка.
Паук:
# -*- 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 ответа
Если бы мне нужно, чтобы мой выходной файл был отсортирован (я предполагаю, что у вас есть веская причина для этого), я бы, вероятно, написал собственный экспортер.
Вот как встроен Scrapy JsonItemExporter
реализовано.
С помощью нескольких простых изменений вы можете изменить его, чтобы добавить элементы в список в export_item()
, а затем отсортировать элементы и записать файл в finish_exporting()
,
Поскольку вы собираете только несколько сотен элементов, недостатки хранения их списка и не записи в файл до завершения сканирования не должны представлять для вас проблемы.
К настоящему времени я нашел рабочее решение с использованием конвейера:
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, вам следует использовать экспорт каналов.