FANZA動画(旧DMM)の動画ページにおけるレビューをスクレイピング

FANZA動画(旧DMM)の動画ページにおけるレビューをスクレイピング プログラミング

今回のスクレイピングの対象は、DMMから名前を変えたFANZA動画です。
でも、やはりDMMの印象が強く残っています。
そのため、FANZA動画と言ってもピンとは来ません。

さて、今回でFANZA動画のスクレイピングを取り扱うのは2回目です。
前回の記事は以下をご覧ください。

本記事の内容

  • 動画(商品)ページにおけるレビューをスクレイピングする理由
  • 動画(商品)ページにおけるレビューのスクレイピング仕様
  • 【サンプルコード】動画(商品)ページにおけるレビューのスクレイピング

それでは、上記に沿って解説していきます。

動画(商品)ページにおけるレビューをスクレイピングする理由

今回は、動画(商品)ページでの商品レビューをスクレイピングしていきます。
ちなみに、前回はレビューワー毎のスクレイピングでした。

前回にレビューワーのページを対象にした理由は、単純に個人的な過去の恨みです。
詳細は、前回記事をご覧ください。

そして、なぜ今回、動画(商品)ページにおけるレビューをスクレイピングするのか?

「FANZA動画のスクレイピングは意外と需要がある」
このことが、当サイトのアクセス分析でわかりました。

それなら、もっと需要のある動画(商品)ページのレビューを対象にしようと。
これが大きな理由です。

動画(商品)ページにおけるレビューのスクレイピング仕様

基本的には、前回記事からは大きく変わりません。
同じように、動的コンテンツ対策としてPythonでSeleniumを使います。

そして、やはり今回も改ページ処理がポイントになります。

「次へ」ボタン(リンク)をSeleniumでクリックして、遷移します。
遷移と言っても、実際にページ遷移は発生しません。

JavaScriptにより、コンテンツを取得して、表示し直しているだけです。
簡潔に言えば、「Ajaxで動的コンテンツ対応している」ですね。

前回も、ここまでは対応しています。
ただ、前回までの対応だと不備があることが判明しました。

運が良ければ、別にそのままでも問題はありません。
でも、サイトが遅い場合などには、問題として発覚してきます。

それは、タイミングの問題です。
「次へ」をクリックして、コンテンツを取得しようとする際のタイミングです。

「次へ」をクリックして、すぐにコンテンツが表示されれば何も問題ありません。
しかし、コンテンツが表示される前にスクレイピングすればどうなるでしょうか?

それは、全く同じコンテンツが取得できるだけです。
そこで、今回はこのタイミングの問題の対応を行ないます。

コンテンツの更新を待ってスクレイピングする

以下は、レビュー部分のhtmlタグです。

「次へ」ボタンをクリックすると、id=reviewの内部を丸ごと置き換えています。
その中に改ページ部分やレビューが記述されています。

この状況において、キーとなるのは「前へ」ボタンです。

「前へ」ボタンがクリックできるようになっていれば、レビュー(コンテンツ)の更新が完了しているはずです。
このような処理を今回のコードでは追加しています。

【サンプルコード】動画(商品)ページにおけるレビューのスクレイピング

サンプルコードは以下。
2020年09月25日時点は、動いています。

from selenium import webdriver
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.common.by import By
import bs4
import re
import time

CONTENT_ID = "jufd00558"
FANZA_BASE = "https://www.dmm.co.jp/digital/videoa/-/detail/=/"
CHROMEDRIVER = "chromedriver.exeのパス"


def get_driver(init_flg):
    
    # ヘッドレスモードでブラウザを起動
    options = Options()
    options.add_argument('--headless')


    # ブラウザーを起動
    driver = webdriver.Chrome(CHROMEDRIVER, options=options)
    
    return driver


# ページ取得
def get_page_from_url(driver, url):
    
    try:
        # ターゲット
        driver.get(url)
        
        driver.implicitly_wait(10)  # 見つからないときは、10秒まで待つ
        
        text = driver.page_source
            
        return text
    except:
        return None


def get_search_value(ptn, str):
    
    result = re.search(ptn, str)
    
    if result:
        return result.group(1)
    else:
        return None
    
def extract_date(s):
    date_pattern = re.compile('(\d{4})/(\d{1,2})/(\d{1,2})')
    result = date_pattern.search(s)
    if result:
        y, m, d = result.groups()
        
        return str(y) + str(m.zfill(2)) + str(d.zfill(2))
    else:
        return None
# info取得
def get_review_from_text(text):
    
    review = []
    
    if text==None:
        text = driver.page_source
    
    soup = bs4.BeautifulSoup(text, features='lxml')
    
    try:  
        base_elem = soup.find(class_="d-review__list")
        
        # レビュー
        elems = base_elem.find_all(class_="d-review__unit")
        
        for elem in elems:
            r_dict = {}
            
            r_title = None
            r_comment = None
            r_postdate = None
            r_service = None
            r_used = False
            r_reviewer_id = None
            r_rating = None
            
            # レビュータイトル
            target_elem = elem.find(class_="d-review__unit__title")
            if target_elem:
                r_title = target_elem.text
                
            # レビューコメント
            target_elem = elem.find(class_="d-review__unit__comment fn-d-review__unit__caution")
            if target_elem:
                # ネタバレ警告あり
                target_elems = elem.find_all(class_="d-review__unit__comment")
                if target_elems[1]:
                    r_comment = target_elems[1].text
            else:
                # ネタバレ警告なし
                target_elem = elem.find(class_="d-review__unit__comment")
                r_comment = target_elem.text
            
            # レビュー投稿日
            target_elem = elem.find(class_="d-review__unit__postdate")
            if target_elem:
                r_postdate = target_elem.text
                r_postdate = extract_date(r_postdate)
            # カテゴリー
            target_elem = elem.find(class_="d-review__unit__service")
            if target_elem:
                r_service = target_elem.text
                r_service = r_service.replace("-", "")
                
            # 購入実績
            target_elem = elem.find(class_="d-review__unit__used")
            if target_elem:
                r_used = True
                
            # レビューワー
            target_elem = elem.find(class_="d-review__unit__reviewer")
            if target_elem:
                a_tag_elem = target_elem.find("a")
                href = a_tag_elem.get("href")
                r_reviewer_id = get_search_value("id=(.*)\/", href)
                
            # 評価
            target_elems = elem.find_all("span")
            if target_elems[0]:
                rating_dic = target_elems[0].get("class")
                if rating_dic:
                    r_rating = rating_dic[0]
                    r_rating = r_rating.replace("d-rating-", "")
        
            r_dict["title"] = r_title
            r_dict["comment"] = r_comment
            r_dict["postdate"] = r_postdate
            r_dict["service"] = r_service
            r_dict["used"] = r_used
            r_dict["reviewer_id"] = r_reviewer_id
            r_dict["rating"] = r_rating
            
            review.append(r_dict)
    
        return review
    except:
        print("err")
        return None
    
def next_btn_click(driver):
     
    # クラスが読み込まれるまで待機(最大10秒)
    WebDriverWait(driver, 10).until(EC.visibility_of_element_located((By.CLASS_NAME, "d-boxpagenation")))
     
    try:
        elem_pagenation = driver.find_element_by_class_name("d-boxpagenation")
        elem_next = elem_pagenation.find_element_by_link_text("次へ")
         
        if elem_next:
            elem_next.click()
            # 読み込み完了を待つ
            WebDriverWait(driver, 10).until(EC.element_to_be_clickable((By.LINK_TEXT, "前へ")))
            
            return True
        else:
            return False
    except:
        return False
    
if __name__ == "__main__":
    
    # Driver
    driver = get_driver(True)
    
    # cid
    cid = CONTENT_ID
    
    # URL
    content_url = FANZA_BASE + "cid=" + cid + "/"
    
    # htmlソース取得
    content_text = get_page_from_url(driver, content_url)
    
    # レビュー取得
    review_list = []
    review = get_review_from_text(content_text)
    review_list.extend(review)
    
    # 次ページへ
    next_flg = next_btn_click(driver)
    
    while next_flg:
        time.sleep(1)
        # レビュー取得
        review = get_review_from_text(None)
        review_list.extend(review)
        next_flg = next_btn_click(driver)
    
    # ブラウザ停止
    driver.quit()   

必要なモジュール・ライブラリがあれば、コピペで動きます。
対象ページのレビューをすべてスクレイピングします。

基本的に、Pythonコードの説明はしません。
なぜなら、この程度のコードを理解できないなら、スクレイピングはやるべきではありません。

スクレイピングは、使い方を間違えば、相手方サーバーへの攻撃になりかねません。
よって、その内容を理解せずに利用することは危険です。

ただ、Seleniumに関する部分は説明しておきます。
特に「コンテンツの更新を待ってスクレイピングする」の部分ですね。

def next_btn_click(driver):
     
    # クラスが読み込まれるまで待機(最大10秒)
    WebDriverWait(driver, 10).until(EC.visibility_of_element_located((By.CLASS_NAME, "d-boxpagenation")))
     
    try:
        elem_pagenation = driver.find_element_by_class_name("d-boxpagenation")
        elem_next = elem_pagenation.find_element_by_link_text("次へ")
         
        if elem_next:
            elem_next.click()
            # 読み込み完了を待つ
            WebDriverWait(driver, 10).until(EC.element_to_be_clickable((By.LINK_TEXT, "前へ")))
            
            return True
        else:
            return False
    except:
        return False

上記の関数内でコンテンツの更新を待っています。

            elem_next.click()
            # 読み込み完了を待つ
            WebDriverWait(driver, 10).until(EC.element_to_be_clickable((By.LINK_TEXT, "前へ")))

「次へ」ボタンがクリックされた後、「前へ」ボタン(リンク)がクリック可能になるまで待ちます。
10秒以内に「前へ」ボタンがクリック可能にならないと、例外エラーとなります。

なお、他の動画(商品)ページを対象に変更する場合は、以下の値を変更します。

CONTENT_ID = "jufd00558"

この値は、cidとしてサイト内では扱われています。

タイトルとURLをコピーしました