見出し画像

46 【Python Scrapyの学び】 不動産の物件情報を抽出してExcelに出力する方法2 ~カラムDetail解説編~

こんにちは!TechCommitメンバーの友季子です♬今回は、WebサイトのSUUMOさまから物件情報を抽出してExcelに出力する【Detailカラム】についてまとめてみます。特にPython、Scrapyを学んでいる方に役立つ情報をお届けしますので、ぜひ参考にしてくださいね♪



0. 前提

この記事では、PythonのライブラリScrapyを使って、ウェブサイトから物件情報をスクレイピングし、整理してExcelファイルに保存する方法を紹介します。以下のことについて理解しておくと、よりスムーズに進めることができます:

  1. PythonとScrapyの基礎: PythonのプログラミングとScrapyライブラリの使い方。

  2. HTMLの基本構造: HTML内のデータがどのように構造化されているか。

  3. データ整形の重要性: データを整形することで分析やレポート作成が簡単になります。


1. 全体の完成コード(サンプル)

import scrapy
from scrapy.http.response.html import HtmlResponse
from urllib.parse import urljoin
from parsel import SelectorList
import pandas as pd
from datetime import datetime

class SuumoSpider(scrapy.Spider):
    name = "suumo"
    origin = "https://suumo.jp"
    allowed_domains = ["suumo.jp"]
    start_urls = [
        "https://suumo.jp/jj/chintai/ichiran/FR301FC005/?ar=030&bs=040&ta=13&sc=13113&cb=0.0&ct=8.0&mb=0&mt=9999999&et=20&cn=5&shkr1=03&shkr2=03&shkr3=03&shkr4=03&sngz=&po1=25&po2=99&pc=50"
    ]
    
    properties = []  # データを保存するリスト

    async def parse(self, response: HtmlResponse):
        await self.handle_response(response)

        # ページネーション処理
        paginators: SelectorList = response.css(".pagination-parts")
        last_paginator = paginators[-1]
        if last_paginator.css("::text").get() == "次へ":
            next_url = last_paginator.css("a").attrib.get("href")
            if next_url:
                yield scrapy.Request(url=urljoin(self.origin, next_url))

    async def handle_response(self, response: HtmlResponse):
        for row in response.css("div.property"):
            title = row.css("h2.property_inner-title a::text").get()
            rent_price = row.css("div.detailbox-property-point::text").get()
            
            property_table = row.css("div.detailbox > div.detailbox-property > table")
            plan_of_house = property_table.css("td.detailbox-property--col3 div::text").get()
            exclusive_area = property_table.css("td.detailbox-property--col3 div::text")[1].get()
            
            detail_texts = row.css("div.detailbox-note div.detailnote-box div::text").getall()
            detail_texts = [t.strip() for t in detail_texts if t.strip() != ""]
            detail = "\n".join(detail_texts).strip()

            # 住所の取得
            address = self.extract_address(row)

            # スクレイピング結果を一時的にリストに保存
            self.properties.append({
                "Title": title,
                "Rent Price": rent_price,
                "Plan of House": plan_of_house,
                "Exclusive Area": exclusive_area,
                "Detail": detail,
                "Address": address,  # 住所を追加
            })

    def extract_address(self, row):
        # 住所の取得
        address_elem = row.css("td.detailbox-property-col::text").get()
        if address_elem and any(prefecture in address_elem for prefecture in PREFECTURES):
            return address_elem.strip()
        return "住所が見つかりません"

    def close(self, reason):
        """
        スパイダーが全ての処理を完了した際に呼び出される
        """
        self.save_to_excel()

    def save_to_excel(self):
        # pandasを使ってデータをExcelに変換
        df = pd.DataFrame(self.properties)
        
        # 現在の日付と時間をファイル名に組み込む
        current_time = datetime.now().strftime("%Y-%m-%d_%H-%M-%S")
        file_name = f"suumo_data_{current_time}.xlsx"

        # ファイルをダウンロードフォルダ直下に保存
        file_path = f"C:/Users/yukik/Downloads/{file_name}"
        df.to_excel(file_path, index=False)

        self.log(f"Excel file saved: {file_path}")

2. コードのクラスとメソッドの説明

  1. class SuumoSpider(scrapy.Spider):

    • Scrapyのスパイダーを定義するクラスで、ウェブサイトからデータを抽出します。

  2. name = "suumo"

    • スパイダーの名前。Scrapyでスパイダーを識別するために使用します。

  3. origin = "https://suumo.jp"

    • スクレイピング対象の基本URLです。

  4. allowed_domains = ["suumo.jp"]

    • スクレイピングを許可するドメインのリストです。

  5. start_urls

    • スクレイピングを開始する最初のURLリストです。

  6. async def parse(self, response: HtmlResponse):

    • メインのパースメソッド。ウェブサイトからHTMLを取得し、handle_response メソッドに渡します。

  7. await self.handle_response(response)

    • handle_response メソッドを呼び出して、データ処理を行います。

  8. paginators: SelectorList = response.css(".pagination-parts")

    • ページネーションのリンクを取得します。

  9. async def handle_response(self, response: HtmlResponse):

    • 物件情報の各要素を抽出し、リストに保存します。

  10. self.extract_address(row)

    • 住所を抽出する補助メソッドです。

  11. def extract_address(self, row):

    • 住所を抽出するためのメソッド。PREFECTURES リストを使用して、住所が正しいかを確認します。

  12. def close(self, reason):

    • スパイダーが終了したときに呼び出されるメソッド。収集したデータをExcelファイルとして保存します。ダウンロード直下に保存します。

  13. def save_to_excel(self):

    • pandasを使って収集したデータをExcelファイルに保存するメソッドです。

補足
CSSセレクタ: HTML要素を特定するための方法で、例row.css() メソッドなどで使用します。
非同期処理 (async/await): I/O操作を効率的に行うために使用します。Scrapyはこれを活用して高効率でデータ収集を行います。
pandas: データを操作するためのPythonライブラリで、Excelファイルの生成に使います。


3. コードの流れ

  1. データの取得: parse メソッドでウェブサイトからHTMLを取得し、handle_response メソッドに渡します。

  2. データの抽出: handle_response メソッドで、物件情報や詳細な説明を抽出します。住所の取得には extract_address メソッドを使用します。

  3. データの保存: スクレイピングが終了すると、close メソッドが呼ばれ、収集したデータがExcelファイルとして保存されます。※抽出時間などが表記されます。


4. Detailカラムの解説

4-1データ抽出と整形

detail_texts = row.css("div.detailbox-note div.detailnote-box div::text").getall()

row.css("div.detailbox-note div.detailnote-box div::text").getall():
ここで、HTMLの特定の部分からすべてのテキストを取り出し
※css() メソッドで、指定したクラスの要素を探したり、その中のテキストを取り出しをします。

補足
①CSSセレクタを使って、HTML内の特定の要素を選択します。ここでは、detailnote-box クラス内の div 要素からテキストを取得しています。

②row.css("div.detailbox-note div.detailnote-box div::text"):
css() メソッドを使用して、特定のHTML要素を選択します。ここでは、detailnote-box クラス内の div 要素からテキストデータを抽出しています。

③detail_texts = row.css("div.detailbox-note div.detailnote-box div::text").getall():
row.css() メソッドを使って、物件詳細が記載された div 要素内のすべてのテキストデータを取得します。

④.getall():
このメソッドで、選択した要素に含まれるすべてのテキストをリストとして取得します。これにより、複数のテキスト要素が含まれる場合にも対応できます。

4-2データクリーニング

detail_texts = [t.strip() for t in detail_texts if t.strip() != ""]

detail_texts = [t.strip() for t in detail_texts if t.strip() != ""]
各テキストの前後の空白を削除し、空のテキスト(スペースだけのテキスト)を除外します。これにより、意味のある情報だけが残ります。

補足
①リスト内包表記:
各テキスト要素に対して、strip() メソッドを使って余分な空白を削除します。

②if t.strip() != "":
空のテキストをフィルタリングします。これにより、意味のある情報だけが残ります。

4-3テキストの整形

detail = "\n".join(detail_texts).strip()

detail = "\n".join(detail_texts).strip()
"\n".join(detail_texts) で、リスト内のテキストを改行で区切って1つの大きな文字列に結合します。

.strip() で最終的な前後の空白を取り除きます。

補足
①"\n".join(detail_texts):
クリーニングされたテキストリストを改行で結合して、1つの連続した文字列にします。これにより、情報が整然と表示されます。

②.strip():
最後に全体の前後の空白を取り除きます。整形後のテキストが見やすくなるようにします!と言ってもよく分からないので(笑)

例:もしHTMLが次のようだとすると・・・

<div class="detailnote-box">
    <div>駅徒歩5分</div>
    <div>近くにスーパーあり</div>
    <div>日当たり良好</div>
</div>

この処理をすると、次のようなテキストになります:

駅徒歩5分
近くにスーパーあり
日当たり良好

5. おわりに

ここまでお読みいただき、ありがとうございました!今回の記事では、私がScrapyを使ってWebページから物件情報を抽出し、整形するために学んでいることを説明しました。もし、ちょっとでもお役に立てばイイネ💗お願いします。Happy Coding!



この記事が気に入ったらサポートをしてみませんか?