banner
Aglorice

Aglorice

Life is a coding,I will debug it.
github
twitter
telegram
bilibili

携程の人気観光地のレビューを取得する

携程の人気観光地レビューのスクレイピング#

前書き#

最近、コンペティションに参加したため、云貴川のいくつかの省都のすべての都市の人気観光地のレビューと観光地の情報をスクレイピングする必要がありました。ネット上のプロジェクトを見て、ほとんど試してみましたが、操作が面倒で、パラメータを一つずつ探す必要があるものもあり、自分のニーズを満たすものはありませんでした。そこで、自分で書くことにしました。まずは効果を見てみましょう。

スクレイピングしたデータはexcelに保存されます

image

スクレイピング中です

image

少しの間のスクレイピングで、云貴川の三省のすべての都市の人気観光地を成功裏にスクレイピングしました。合計で 28 万件のデータがあります大変でした😭😭😭

以下は今回のスクレイピングの過程を共有します🚀🚀🚀

注意:今回共有するすべてのコードは完全なコードではありません。完全なコードはaglorice/CtripSpider: 携程コメント爬虫,使用线程池来爬取热门景区评论,简单易用。一键爬取任意省的所有热门景区。 (github.com)を参照してください。

1. ページの分析#

まず、携程の攻略.景点ページにアクセスし、カーソルを国内(含港澳台)に移動すると、ほぼすべての省のすべての都市を取得できます。ここが私たちの都市データの出所です。

image

image

コンソールを開くと、すぐに特定できました。以下の通りです。

image

これをもとにコードを書きます。ここではBeautifulSoupを使用してページを解析します。

    def get_areas(self) -> list:
        city_list = []
        try:
            res = self.sees.get(
                url=GET_HOME,
                headers={
                    "User-Agent": get_fake_user_agent("pc")
                },
                proxies=my_get_proxy(),
                timeout=TIME_OUT
            )
        except Exception as e:
            self.console.print(f"[red]都市の観光地情報の取得に失敗しました,{e},ネットワークまたはプロキシを確認してください。", style="bold red")
            exit()
        res_shop = BeautifulSoup(res.text, "lxml")
        areas = res_shop.find_all("div", attrs={"class": "city-selector-tab-main-city"})

        for area in areas:
            area_title = area.find("div", attrs={"class": "city-selector-tab-main-city-title"}).string
            if area_title is None:
                continue
            area_items = area.find_all("div", attrs={"class": "city-selector-tab-main-city-list"})
            area_items_list = [{"name": item.string, "url": item["href"]} for item in area_items[0].find_all("a")]
            city_list.append({
                "name": area_title,
                "city": area_items_list
            })
        return city_list

この方法で指定した省のすべての都市名とurlcity.jsonに保存します。なぜ最初に保存するのかというと、カスタマイズを容易にするためです。必要に応じて、スクレイピングしたい都市を自由に追加したり削除したりできます。スクレイピングの結果は以下の通りです。

image

次に、これらの観光地のurlを開きます。以下のように、ホームページには人気の観光地やスポットが表示されます。

image

前準備は完了しました。これから対応する観光地のレビューをスクレイピングしましょう。

2. 観光地レビューのスクレイピング#

image

適当に観光地のレビューを開き、コンソールでリクエストを確認します。以下の通りです:

image

まず、パラメータを分析します。何度も試行した結果、動的なものがわかりました。まずは_fxpcqlniredtです。クッキーを確認するとすぐに見つかります。

image

次にx-traceIDです。JS の逆アセンブルを通じて、関連するコードを直接見つけました。以下の通りです:

image-20230728192940229

生成方法がわかったので、簡単です。コードを書きます。

    def generate_scene_comments_params(self) -> dict:
        """
        観光地レビューのリクエストパラメータを生成します
        :return:
        """
        random_number = random.randint(100000, 999999)
        return {
            "_fxpcqlniredt": self.sees.cookies.get("GUID"),
            "x-traceID": self.sees.cookies.get("GUID") + "-" + str(int(time.time() * 1000000)) + "-" + str(
                random_number)
        }

実際、ここまで来ればほぼ完了です。あとはpoildの問題を解決するだけです。実はこのパラメータは各ページのscriptタグにあります。しかし、これではリクエストが一回増えて時間が無駄になります。データを直接リクエストできればいいのですが、観光地ページに入る必要はありません。そこで考え方を変えます。携程のh5ページにアクセスします。レビュー取得インターフェースがpc端末とは異なることがわかりました。以下の通りです:

image

モバイル端ではpoildパラメータを使用する必要がありません。実際、ここで終了です。残りはスクレイピング中のさまざまな問題を解決することです。最も重要なのは携程の対スクレイピング対策です。私はスピードを上げるためにスレッドプールを使用したため、非常に速く、これを解決するためにランダムなuaとプロキシプールを使用し、さまざまなエラーハンドリングメカニズムを加え、スクレイピングが安定して実行できるようにしました。以下はインターフェースへの頻繁なアクセスの結果です:

image

3. 携程の対スクレイピング対策の解決方法#

対スクレイピングの最初の解決策はランダムなuaです。以前はfake-useragentを使用していましたが、後にh5インターフェースを使用するため、uaはモバイル端のものでなければなりませんでした。このライブラリはサポートしていないため、自分で手動で作成しました。シンプルですが実用的です。

# -*- coding = utf-8 -*-
# @Time :2023/7/13 21:32
# @Author :小岳
# @Email  :[email protected]
# @PROJECT_NAME :scenic_spots_comment
# @File :  fake_user_agent.py
from fake_useragent import UserAgent
import random
from config import IS_FAKE_USER_AGENT


def get_fake_user_agent(ua: str, default=True) -> str:
    match ua:
        case "mobile":
            if IS_FAKE_USER_AGENT and default:
                ua = get_mobile_user_agent()
                return ua
            else:
                return "Mozilla/5.0 (Linux; Android 8.0.0; SM-G955U Build/R16NW) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.141 Mobile Safari/537.36 Edg/114.0.0.0"
        case "pc":
            if IS_FAKE_USER_AGENT and default:
                ua = UserAgent()
                return ua.random
            else:
                return "Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.5060.114 Mobile Safari/537.36 Edg/103.0.1264.49"


def get_mobile_user_agent() -> str:
    platforms = [
        'iPhone; CPU iPhone OS 14_6 like Mac OS X',
        'Linux; Android 11.0.0; Pixel 5 Build/RD1A.201105.003',
        'Linux; Android 8.0.0; Pixel 5 Build/RD1A.201105.003',
        'iPad; CPU OS 14_6 like Mac OS X',
        'iPad; CPU OS 15_6 like Mac OS X',
        'Linux; U; Android 9; en-us; SM-G960U Build/PPR1.180610.011',  # Samsung Galaxy S9
        'Linux; U; Android 10; en-us; SM-G975U Build/QP1A.190711.020',  # Samsung Galaxy S10
        'Linux; U; Android 11; en-us; SM-G998U Build/RP1A.200720.012',  # Samsung Galaxy S21 Ultra
        'Linux; U; Android 9; en-us; Mi A3 Build/PKQ1.180904.001',  # Xiaomi Mi A3
        'Linux; U; Android 10; en-us; Mi 10T Pro Build/QKQ1.200419.002',  # Xiaomi Mi 10T Pro
        'Linux; U; Android 11; en-us; LG-MG870 Build/RQ1A.210205.004',  # LG Velvet
        'Linux; U; Android 11; en-us; ASUS_I003D Build/RKQ1.200826.002',  # Asus ROG Phone 3
        'Linux; U; Android 10; en-us; CLT-L29 Build/10.0.1.161',  # Huawei P30 Pro
    ]

    browsers = [
        'Chrome',
        'Firefox',
        'Safari',
        'Opera',
        'Edge',
        'UCBrowser',
        'SamsungBrowser'
    ]

    platform = random.choice(platforms)
    browser = random.choice(browsers)

    match browser:
        case 'Chrome':
            version = random.randint(70, 90)
            return f'Mozilla/5.0 ({platform}) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/{version}.0.#{random.randint(1000, 9999)}.#{random.randint(10, 99)} Mobile Safari/537.36'

        case 'Firefox':
            version = random.randint(60, 80)
            return f'Mozilla/5.0 ({platform}; rv:{version}.0) Gecko/20100101 Firefox/{version}.0'

        case 'Safari':
            version = random.randint(10, 14)
            return f'Mozilla/5.0 ({platform}) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/{version}.0 Safari/605.1.15'

        case 'Opera':
            version = random.randint(60, 80)
            return f'Mozilla/5.0 ({platform}) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/{version}.0.#{random.randint(1000, 9999)}.#{random.randint(10, 99)} Mobile Safari/537.36 OPR/{version}.0'

        case 'Edge':
            version = random.randint(80, 90)
            return f'Mozilla/5.0 ({platform}) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/{version}.0.#{random.randint(1000, 9999)}.#{random.randint(10, 99)} Mobile Safari/537.36 Edg/{version}.0'

        case 'UCBrowser':
            version = random.randint(12, 15)
            return f'Mozilla/5.0 ({platform}) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.26 UBrowser/{version}.1.2.49 Mobile Safari/537.36'

        case 'SamsungBrowser':
            version = random.randint(10, 14)
            return f'Mozilla/5.0 ({platform}) AppleWebKit/537.36 (KHTML, like Gecko) SamsungBrowser/{version}.0 Chrome/63.0.3239.26 Mobile Safari/537.36'

残るのはスレッドプールです。ここで使用するのはオープンソースプロジェクトjhao104/proxy_pool: Python 爬虫代理 IP 池 (proxy pool) (github.com)です。

ここまで解決すれば、ほぼ完了です。👀👀👀

4. 結論#

今回の携程のスクレイピングを通じて、問題に直面したときには思考を広げることが重要であり、うまくいかない場合は多くの試行を行うことができるとまとめることができます。

プロジェクトのアドレス aglorice/CtripSpider: 携程コメント爬虫,使用线程池来爬取热门景区评论,简单易用。一键爬取任意省的所有热门景区。 (github.com)

読み込み中...
文章は、創作者によって署名され、ブロックチェーンに安全に保存されています。