質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

ただいまの
回答率

88.91%

Python3 Scrapyでレビューサイトからレビューを取得したい。

受付中

回答 0

投稿

  • 評価
  • クリップ 0
  • VIEW 148

Malo

score 19

現在、Python3を利用しScrapyで「もぐナビ (https://mognavi.jp/okashi/product)」というサイトからお菓子のレビューを取得し、それをlocalのサーバのMySQLに保存するという作業を行っています。
しかし、この中のレビューで一部レビュー文全体を取得できないものがあり、その原因が分からず困っています。解決方法がありましたら、お教え頂けると幸いです。

以下がソースコードです。まだ作成途中なので、Itemで使っていない変数などもあります。

[items.py]

# -*- coding: utf-8 -*-

from scrapy import Item, Field

# 対象の食品に関する情報
class Food(Item):
    food_category = Field()
    food_brand = Field()
    food_name = Field()
    maker_name = Field()
    pass

# レビュー及びレビュアーに関する情報
class Review(Item):
    food_name = Field()
    review_title = Field()
    review_text = Field()
    user_id = Field()
    user_name = Field()
    date = Field()
    pass

# FoodReviewを包括
class Info(Item):
    food = Field()
    review = Field()

[spider.py]

# -*- coding: utf-8 -*-
import scrapy
from test_scrapy.items import Food, Review, Info

class ScrapyBlogSpiderSpider(scrapy.Spider):
    name = 'scrapy_blog_spider'
    allowed_domains = ['mognavi.jp']
    start_urls = ['https://mognavi.jp/okashi/product/']

    # トップページ用parse。 商品ラインナップを漁る。
    def parse(self, response):
        for product in response.css('#searchResList > ul > li'):
            product_link = response.urljoin(product.css('.txt h3 > a::attr(href)').extract_first())
            yield scrapy.Request(product_link, callback=self.parse_food)

        # 次ページの情報を取得
        links = response.css('#mainCol .cnt .nam a')
        for l in links:
            if l.css('a::text').extract_first()=='次の20件を見る':
                next_link = response.urljoin(l.css('a::attr(href)').extract_first())

        # 次ページがあれば遷移
        if next_link:
            yield scrapy.Request(next_link, callback=self.parse)


    # 食品情報用parse。 食品情報を漁る。
    def parse_food(self, response):
        Food_info = Food()

        # 食品に関する情報を取得
        info = response.css('.dataTable')
        key = info.css('tr > th::text').extract()
        if '内容量・参考価格' in key:
            key.remove('内容量・参考価格')
        value = info.css('tr > td > a::text').extract()

        for i, k in enumerate(key):
            if k=='カテゴリー':
                Food_info['food_category'] = value[i]
            elif k=='メーカー':
                Food_info['maker_name'] = value[i]
            elif k=='ブランド':
                Food_info['food_brand'] = value[i]
        Food_info['food_name'] = response.css('.system-h2-container .fn span::text').extract_first().replace(Food_info['maker_name'], '').strip()

        # 口コミのリンクへ移動
        kuchikomi_link = response.urljoin(response.css('.productPage-tab #tabsBtn li:nth-child(2) a::attr(href)').extract_first())
        yield scrapy.Request(kuchikomi_link, callback=self.parse_review, meta={'Food_info': Food_info})


    # レビュー情報用parse。
    def parse_review(self, response):
        Food_info = response.meta['Food_info']

        # レビュー情報を取得
        for review in response.css('#pKutikomi .kutikomi'):
            # レビュー1件の情報を格納
            Review_info = Review(
                review_title=''.join([t.strip() for t in review.css('.clearfix .txt .in .title a::text').extract()]),
                review_text=' '.join([t.strip() for t in review.css('.clearfix .txt .in .infukidashi p::text').extract()]),
                date=review.css('.clearfix .txt .in .title p::text').extract_first().strip()
            )
            # レコードを出力
            yield Info(
                food = Food_info,
                review = Review_info
            )

        # 次のページへのリンクを取得し、あれば遷移して繰り返す
        links = response.css('#wrapperNew #mainCol .cntNam a')
        next_link = ''
        for l in links:
            if l.css('a::text').extract_first() == '次の20件を見る':
                next_link = response.urljoin(l.css('a::attr(href)').extract_first())
        if next_link:
            yield scrapy.Request(next_link, callback=self.parse_review, meta={'Food_info': Food_info})

[pipelines.py]

# -*- coding: utf-8 -*-

import mysql.connector as connector
from datetime import datetime

class TestScrapyPipeline:
    _db = None

    @classmethod
    def get_database(cls):
        # DBへの接続
        cls._db = connector.connect(host='localhost',
                                    user='user',
                                    password='password',
                                    database='database')
        # DBの自動再接続設定
        cls._db.ping(reconnect=True)

        return cls._db


    '''
    Pipelineにデータが渡される時に実行される
    itemにSpiderから渡されたitemがセットされる
    '''
    def process_item(self, item, spider):
        self.save_post(item)
        return item


    '''
    itemをDBに保存する
    '''
    def save_post(self, item):
        Food_info = item['food']
        Review_info = item['review']

        db = self.get_database()
        cursor = db.cursor()

        ### 食品情報に関して ###
        if not self.find_post_food(Food_info['food_name']): # 既に同じレコードがある場合はスキップ
            cursor.execute(
                'INSERT INTO Food (food_name, food_brand, food_category) VALUES (%s, %s, %s)', (
                    Food_info['food_name'],
                    Food_info['food_brand'] if 'food_brand' in Food_info else '',
                    Food_info['food_category']
                )
            )

        ### レビューに関して ###
        if self.find_post_review(Review_info['review_text']): #既に同じレコードがある場合はスキップ
            return

        # Foodテーブルのidを引っ張ってくる
        cursor.execute(
            'SELECT food_id FROM Food WHERE food_name=%s', (Food_info['food_name'], )
        )
        food_id = cursor.fetchone()[0]

        # Reviewテーブルにレコードを追加
        cursor.execute(
            'INSERT INTO Review (food_id, review_title, review_text, date) VALUES (%s, %s, %s, %s)', (
                food_id,
                Review_info['review_title'],
                Review_info['review_text']
            )
        )

        db.commit()


    '''
    Foodテーブルで同じレコードがあるかどうかを確認する
    '''
    def find_post_food(self, info):
        db = self.get_database()
        cursor = db.cursor()
        cursor.execute(
            "SELECT * FROM Food WHERE food_name=%s", (info, )
        )

        return cursor.fetchone()

    '''
    Reviewテーブルで同じレコードがあるかどうかを確認する
    '''
    def find_post_review(self, info):
        db = self.get_database()
        cursor = db.cursor()
        cursor.execute(
            "SELECT * FROM Review WHERE review_text=%s", (info, )
        )

        return cursor.fetchone()

追記する必要のある情報がありましたら、ご指摘頂けると幸いです。

  • 気になる質問をクリップする

    クリップした質問は、後からいつでもマイページで確認できます。

    またクリップした質問に回答があった際、通知やメールを受け取ることができます。

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 過去に投稿した質問と同じ内容の質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

まだ回答がついていません

15分調べてもわからないことは、teratailで質問しよう!

  • ただいまの回答率 88.91%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる

関連した質問

同じタグがついた質問を見る