スクレイピングフレームワークscrapyを使用して、あるコンビニ商品のデータをJSON出力したいです。
ファイル構成を以下に示します。
const
1# ドメイン 2DOMAIN = "xxx.co.jp" 3# URL 4URL = "https://www.xxx.co.jp/" 5# 商品グループのlink_prefix 6GROUP_LINK_PREFIX = "/products/a/cat/" 7# 商品のカテゴリーのlink_prefix 8PRODUCT_LIST_CLASS_PREFIX = "list_inner -item-code-" 9# 商品名のdiv要素のclass名 10PRODUCT_TTL_CLASS = "item_ttl" 11# 商品名のdiv要素のclass名 12PRODUCT_PRICE_CLASS = "item_price" 13# 商品名のdiv要素のclass名 14PRODUCT_REGION_CLASS = "item_region" 15# 商品名のdiv要素のclass名 16PRODUCT_LAUNCH_CLASS = "item_launch" 17# ページャーのdiv要素のclass名 18PRODUCT_PAGER_CLASS = "pager" 19# 商品一覧の"次へ"のリンクテキスト 20PRODUCT_NEXT_PAGE = "次へ" 21# 販売地域が文字列でしゅとくされるので、配列にするため区切り文字を定義 22SALES_AREA_DELIMITER = "、" 23# 税込価格のみを取得するための正規表現 24INCLUDED_TAX_PRICE_REGEXP = "(?<=税込)[0-9]+" 25 26# "おにぎり"リンクのテキスト 27ONIGIRI = "おにぎり"
spider
1import scrapy 2import app.const as const 3from app.items import ConvenienceStoreProduct 4 5 6class OnigiriSpider(scrapy.Spider): 7 name = 'onigiri' 8 allowed_domains = [const.DOMAIN] 9 start_urls = [const.URL] 10 11 def parse(self, response): 12 """'おにぎり'リンクをたどるために初めに呼ばれる 13 """ 14 onigiri_category_relative_path = response.xpath('//a[contains(text(), "' + const.ONIGIRI + '")]/@href').get() 15 yield scrapy.Request( 16 response.urljoin(onigiri_category_relative_path), 17 callback = self.onigiri_group_parse 18 ) 19 20 def onigiri_group_parse(self, response): 21 """'おにぎり'のグループリンクをたどるために呼ばれる 22 """ 23 onigiri_group_relative_paths = response.xpath('//a[starts-with(@href, "' + const.GROUP_LINK_PREFIX + '")]/@href').getall() 24 for path in onigiri_group_relative_paths: 25 yield scrapy.Request( 26 response.urljoin(path), callback = self.product_parse) 27 28 def product_parse(self, response): 29 """商品データを抽出 30 """ 31 for sel in response.xpath('//div[starts-with(@class, "' + const.PRODUCT_LIST_CLASS_PREFIX + '")]'): 32 yield ConvenienceStoreProduct( 33 name = sel.css('.' + const.PRODUCT_TTL_CLASS).xpath('./p/a/text()').get().replace(" ", " "), 34 included_tax_price = sel.css('.' + const.PRODUCT_PRICE_CLASS).xpath('./p/text()').re_first(r'' + const.INCLUDED_TAX_PRICE_REGEXP + ''), 35 sales_area = sel.css('.' + const.PRODUCT_REGION_CLASS).xpath('./p/text()').get().split(const.SALES_AREA_DELIMITER), 36 product_detail_link = sel.css('.' + const.PRODUCT_TTL_CLASS).xpath('./p/a/@href').get(), 37 img_link = sel.css('figure').xpath('./a/img/@data-original').get(), 38 ) 39 # "次へ"のリンクがある場合はそのリンクをたどって、このメソッドを再帰的に呼び出す 40 next_page = response.css('.' + const.PRODUCT_PAGER_CLASS).xpath('//a[contains(text(), "' + const.PRODUCT_NEXT_PAGE + '")]/@href').get() 41 if next_page: 42 yield scrapy.Request(response.urljoin(next_page), self.product_parse)
item
1# Define here the models for your scraped items 2# 3# See documentation in: 4# https://docs.scrapy.org/en/latest/topics/items.html 5 6from dataclasses import dataclass 7 8@dataclass 9class ConvenienceStoreProduct: 10 name: str 11 included_tax_price: int 12 sales_area: list 13 product_detail_link: str 14 img_link: str
json
1// 期待するJSON 2{ 3 "onigiri": [ 4 { 5 "name": "具たっぷり手巻 海老マヨネーズ", 6 "included_tax_price": 135, 7 "sales_area": ["北海道", "福島県", "関東", "新潟県", "北陸"], 8 "product_detail_link": "/products/a/item/045578/", 9 "img_link": "https://img.7api-01.dp1.sej.co.jp/item-image/045578/F04CBC825AEC3EE2A91BF51AC50AF7A7.jpg" 10 }, 11 { 12 "name": "鯛だし飯手巻おにぎり すだち香る鯛塩焼き", 13 "included_tax_price": 151, 14 "sales_area": ["鳥取県", "島根県", "岡山県", "広島県", "四国"], 15 "product_detail_link": "/products/a/item/045419/", 16 "img_link": "https://img.7api-01.dp1.sej.co.jp/item-image/045419/D832C7C08CFCD5FD8CB443AC64CC1EB7.jpg" 17 } 18... 19 ] 20}
上記のpythonコードでは期待通りのJSONが出力できず、onigiri_group_parse
メソッドとproduct_parse
メソッドの間でうまくループして、"onigiri"をキーとした商品配列のJSONを出力したいです。(onigiri_group_parse
で処理しているおにぎりのグループリンクが3つほどあり、それらの商品リンクをすべて辿って"onigiri"をキーとした商品配列の中に全部入れたいです。)ざっくりとした質問で大変申し訳ないのですが、何か実現できる良い実装案はございませんでしょうか? ご回答頂けると大変助かります、、よろしくお願いいたします。
バージョン
python:3.9.1
Scrapy:2.4.1
以下詳細情報追加
==================================================================
具体的なHTMLとしましては、以下のリンクから得られるHTMLページです。
https://www.sej.co.jp/products/a/onigiri/
このページに今
・「手巻きおにぎり」のラインナップを見る
・「直巻きおにぎり」のラインナップを見る
・「その他のおむすび」のラインナップを見る
の3つのリンクがあるのですが、それらのリンクを追うため、
onigiri_group_parse
でリクエストを送っています。
なんとかうまくループして、上記の期待通りのJSONにしたいのですが、
現在
{ "onigiri": [ { "name": "具たっぷり手巻 海老マヨネーズ", "included_tax_price": 135, "sales_area": ["北海道", "福島県", "関東", "新潟県", "北陸"], "product_detail_link": "/products/a/item/045578/", "img_link": "https://img.7api-01.dp1.sej.co.jp/item-image/045578/F04CBC825AEC3EE2A91BF51AC50AF7A7.jpg" }, { "name": "鯛だし飯手巻おにぎり すだち香る鯛塩焼き", "included_tax_price": 151, "sales_area": ["鳥取県", "島根県", "岡山県", "広島県", "四国"], "product_detail_link": "/products/a/item/045419/", "img_link": "https://img.7api-01.dp1.sej.co.jp/item-image/045419/D832C7C08CFCD5FD8CB443AC64CC1EB7.jpg" } ], "onigiri": [ { "name": "具たっぷり手巻 海老マヨネーズ", "included_tax_price": 135, "sales_area": ["北海道", "福島県", "関東", "新潟県", "北陸"], "product_detail_link": "/products/a/item/045578/", "img_link": "https://img.7api-01.dp1.sej.co.jp/item-image/045578/F04CBC825AEC3EE2A91BF51AC50AF7A7.jpg" }, { "name": "鯛だし飯手巻おにぎり すだち香る鯛塩焼き", "included_tax_price": 151, "sales_area": ["鳥取県", "島根県", "岡山県", "広島県", "四国"], "product_detail_link": "/products/a/item/045419/", "img_link": "https://img.7api-01.dp1.sej.co.jp/item-image/045419/D832C7C08CFCD5FD8CB443AC64CC1EB7.jpg" } ], ... }
このように"onigiri"のキーをいくつか(今回は3つ分)持った形になるか、
{ { "name": "具たっぷり手巻 海老マヨネーズ", "included_tax_price": 135, "sales_area": ["北海道", "福島県", "関東", "新潟県", "北陸"], "product_detail_link": "/products/a/item/045578/", "img_link": "https://img.7api-01.dp1.sej.co.jp/item-image/045578/F04CBC825AEC3EE2A91BF51AC50AF7A7.jpg" }, { "name": "鯛だし飯手巻おにぎり すだち香る鯛塩焼き", "included_tax_price": 151, "sales_area": ["鳥取県", "島根県", "岡山県", "広島県", "四国"], "product_detail_link": "/products/a/item/045419/", "img_link": "https://img.7api-01.dp1.sej.co.jp/item-image/045419/D832C7C08CFCD5FD8CB443AC64CC1EB7.jpg" } }
このように仕方なく"onigiri"というキーを持たせず出力するようにしかできない状況です。
最初に載せた期待通りのJSONの通り"onigiri"のキーを一つにして、その値の配列にすべてのおにぎり商品データを入れたいです。
情報が不足しており、すみません。
あなたの回答
tips
プレビュー