Это мой первый скрап-проект - и по общему признанию, одно из моих первых упражнений с питоном.Я ищу способ очистить несколько дочерних страниц, объединить / добавить содержимое в одно значение и передать данные BACK / UP исходной родительской странице.Число дочерних страниц на родительскую страницу также является переменным - оно может быть всего 1, но никогда не будет 0 (возможно, полезно для обработки ошибок?).Кроме того, дочерние страницы могут повторяться и появляться вновь, поскольку они НЕ являются эксклюзивными для одного из родителей.Мне удалось передать метаданные родительской страницы DOWN на соответствующие дочерние страницы, но я остался в тупике при выполнении обратного.
Вот пример структуры страницы:
Top Level Domain
- Pagination/Index Page #1 (parse recipe links)
- Recipe #1 (select info & parse ingredient links)
- Ingredient #1 (select info)
- Ingredient #2 (select info)
- Ingredient #3 (select info)
- Recipe #2
- Ingredient #1
- Recipe #3
- Ingredient #1
- Ingredient #2
- Pagination/Index Page #2
- Recipe #N
- Ingredient #N
- ...
- Pagination/Index Page #3
- ... continued
Вывод, который я ищу (по рецепту) выглядит примерно так:
{
"recipe_title": "Gin & Tonic",
"recipe_posted_date": "May 2, 2019",
"recipe_url": "www.XYZ.com/gandt.html",
"recipe_instructions": "<block of text here>",
"recipe_ingredients": ["gin", "tonic water", "lime wedge"],
"recipe_calorie_total": "135 calories",
"recipe_calorie_list": ["60 calories", "70 calories", "5 calories"]
}
Я извлекаю URL каждого ингредиента со страницы соответствующего рецепта.Мне нужно извлечь количество калорий из каждой страницы ингредиента, объединить его с количеством калорий других ингредиентов и в идеале получить один элемент.Поскольку один ингредиент не является эксклюзивным для одного рецепта, мне нужно иметь возможность повторно посетить страницу с ингредиентами позже в моем сканировании.
(примечание - это не реальный пример, поскольку количество калорий, очевидно, варьируется в зависимости отпо объему, необходимому для рецепта)
Мой опубликованный код приближает меня к тому, что я ищу, но я должен представить, что есть более изящный способ решения проблемы.Размещенный код успешно передает ВНИЗ метаданных рецепта на уровень ингредиентов, просматривает ингредиенты и добавляет количество калорий.Поскольку информация передается, я уступаю на уровне ингредиентов и создаю несколько дубликатов рецептов (по одному на ингредиент), пока не переберу последний ингредиент.На этом этапе я собираюсь добавить индекс индекса ингредиента, чтобы каким-то образом сохранить запись с наибольшим индексом ингредиента # на URL рецепта.Прежде чем я добрался до этого момента, я решил обратиться к профессионалам за советом.
Код скребка:
import scrapy
from scrapy.spiders import CrawlSpider, Rule
from scrapy.linkextractors import LinkExtractor
from recipe_scraper.items import RecipeItem
class RecipeSpider(CrawlSpider):
name = 'Recipe'
allowed_domains = ['www.example.com']
start_urls = ['https://www.example.com/recipes/']
rules = (
Rule(
LinkExtractor(
allow=()
,restrict_css=('.pagination')
,unique=True
)
,callback='parse_index_page'
,follow=True
),
)
def parse_index_page(self, response):
print('Processing Index Page.. ' + response.url)
index_url = response.url
recipe_urls = response.css('.recipe > a::attr(href)').getall()
for a in recipe_urls:
request = scrapy.Request(a, callback=self.parse_recipe_page)
request.meta['index_url'] = index_url
yield request
def parse_recipe_page(self, response):
print('Processing Recipe Page.. ' + response.url)
Recipe_url = response.url
Recipe_title = response.css('.Recipe_title::text').extract()[0]
Recipe_posted_date = response.css('.Recipe_posted_date::text').extract()[0]
Recipe_instructions = response.css('.Recipe_instructions::text').extract()[0]
Recipe_ingredients = response.xpath('//ul[@class="ingredients"]//li[@class="ingredient"]/a/text()').getall()
Recipe_ingredient_urls = response.xpath('//ul[@class="ingredients"]//li[@class="ingredient"]/a/@href').getall()
Recipe_calorie_list_append = []
Recipe_calorie_list = []
Recipe_calorie_total = []
Recipe_item = RecipeItem()
Recipe_item['index_url'] = response.meta["index_url"]
Recipe_item['Recipe_url'] = Recipe_url
Recipe_item['Recipe_title'] = Recipe_title
Recipe_item['Recipe_posted_date'] = Recipe_posted_date
Recipe_item['Recipe_instructions'] = Recipe_instructions
Recipe_item['Recipe_ingredients'] = Recipe_ingredients
Recipe_item['Recipe_ingredient_urls'] = Recipe_ingredient_urls
Recipe_item['Recipe_ingredient_url_count'] = len(Recipe_ingredient_urls)
Recipe_calorie_list.clear()
Recipe_ingredient_url_index = 0
while Recipe_ingredient_url_index < len(Recipe_ingredient_urls):
ingredient_request = scrapy.Request(Recipe_ingredient_urls[Recipe_ingredient_url_index], callback=self.parse_ingredient_page, dont_filter=True)
ingredient_request.meta['Recipe_item'] = Recipe_item
ingredient_request.meta['Recipe_calorie_list'] = Recipe_calorie_list
yield ingredient_request
Recipe_calorie_list_append.append(Recipe_calorie_list)
Recipe_ingredient_url_index += 1
def parse_ingredient_page(self, response):
print('Processing Ingredient Page.. ' + response.url)
Recipe_item = response.meta['Recipe_item']
Recipe_calorie_list = response.meta["Recipe_calorie_list"]
ingredient_url = response.url
ingredient_calorie_total = response.css('div.calorie::text').getall()
Recipe_calorie_list.append(ingredient_calorie_total)
Recipe_item['Recipe_calorie_list'] = Recipe_calorie_list
yield Recipe_item
Recipe_calorie_list.clear()
Как и раньше, мой неидеальный результат выглядит следующим образом (примечаниесписок калорий):
{
"recipe_title": "Gin & Tonic",
"recipe_posted_date": "May 2, 2019",
"recipe_url": "www.XYZ.com/gandt.html",
"recipe_instructions": "<block of text here>",
"recipe_ingredients": ["gin", "tonic water", "lime wedge"],
"recipe_calorie_total": [],
"recipe_calorie_list": ["60 calories"]
},
{
"recipe_title": "Gin & Tonic",
"recipe_posted_date": "May 2, 2019",
"recipe_url": "www.XYZ.com/gandt.html",
"recipe_instructions": "<block of text here>",
"recipe_ingredients": ["gin", "tonic water", "lime wedge"],
"recipe_calorie_total": [],
"recipe_calorie_list": ["60 calories", "70 calories"]
},
{
"recipe_title": "Gin & Tonic",
"recipe_posted_date": "May 2, 2019",
"recipe_url": "www.XYZ.com/gandt.html",
"recipe_instructions": "<block of text here>",
"recipe_ingredients": ["gin", "tonic water", "lime wedge"],
"recipe_calorie_total": [],
"recipe_calorie_list": ["60 calories", "70 calories", "5 calories"]
}