某程旅行小程序爬虫技术解析与实战案例

一、小程序爬虫核心技术认知

1. 小程序与传统 Web 爬虫的核心差异

传统 Web 爬虫主要针对 PC 端或移动端网页,基于 HTML 解析、Cookie 维持、HTTP/HTTPS 请求模拟即可完成大部分数据抓取工作。而小程序爬虫的核心差异体现在三个方面:

  1. 传输协议与数据格式:小程序后端接口多采用 JSON 格式传输数据,极少返回完整 HTML 页面,无需进行 DOM 解析,重点在于接口参数解密与响应数据提取;
  2. 身份验证机制 :小程序普遍采用 <font style="color:rgb(31, 35, 41);background-color:rgba(0, 0, 0, 0);">wx.login</font> 生成的 code、session_key、openid 等进行用户身份验证,部分接口还会携带自定义签名(如 sign 参数),防止非法请求;
  3. 抓包与调试限制:小程序运行在微信的沙箱环境中,默认禁止外部工具直接抓包,且存在反爬机制(如接口频率限制、设备指纹验证、参数时效性校验)。

2. 小程序爬虫必备核心技术栈

完成某程旅行小程序爬虫开发,需要掌握以下核心技术:

  1. 抓包技术:使用 Fiddler、Charles 等工具配置代理,突破微信沙箱环境限制,捕获小程序的网络请求;
  2. 接口分析技术:解析请求头、请求参数、响应数据的结构,识别加密参数(如 sign、timestamp、nonce)的生成逻辑;
  3. 请求模拟技术:基于 Python 的 requests、aiohttp 库模拟小程序的 HTTP/HTTPS 请求,维持会话状态;
  4. 数据处理技术:使用 json、pandas 库解析、清洗、存储抓取到的 JSON 数据;
  5. 反反爬应对技术:实现请求频率控制、UA 伪装、签名参数模拟等,规避小程序的反爬策略。

二、前期准备工作

1. 环境与工具搭建

  1. 开发环境:Python 3.8+(兼容性好,第三方库生态完善),推荐使用 Anaconda 管理环境;
  2. 抓包工具:Charles(跨平台,操作友好,适合小程序抓包);
  3. Python 第三方库:安装必备依赖
  • requests:模拟 HTTP/HTTPS 请求;
  • pandas:数据清洗与存储;
  • pycryptodome:应对可能的接口数据加密(如 AES、DES);
  • fake-useragent:生成随机 User-Agent,伪装请求来源。

2. Charles 抓包配置(关键步骤)

小程序运行在微信环境中,需配置代理才能捕获其网络请求,步骤如下:

  1. 打开 Charles,进入「Proxy」→「Proxy Settings」,设置端口(如 8888),勾选「Enable transparent HTTP proxying」;
  2. 配置电脑本机代理,将 HTTP/HTTPS 代理指向 Charles 的 IP 与端口(本地 IP+8888);
  3. 安装 Charles 证书:进入「Help」→「SSL Proxying」→「Install Charles Root Certificate」,完成证书信任配置(重点:移动端 / 微信需安装对应证书);
  4. 微信小程序抓包配置:打开手机微信(或电脑微信模拟器),连接与电脑同一局域网,配置手机代理为电脑 IP+8888,安装 Charles 手机证书;
  5. 开启 SSL 代理:在 Charles 中右键点击目标请求(某程旅行小程序相关),选择「Enable SSL Proxying」,即可捕获 HTTPS 加密请求。

3. 合规性声明

重要提醒:本文所有技术内容仅用于技术研究与学习,不得用于商业用途或非法数据抓取。

  1. 某程旅行小程序的所有数据均受《中华人民共和国著作权法》《网络安全法》保护,抓取前需确认是否获得平台授权;
  2. 不得突破平台的反爬机制,不得进行高频次请求影响平台服务器正常运行;
  3. 抓取的数据不得用于传播、售卖、竞品分析等违规场景,仅可用于个人技术研究。

三、某程旅行小程序接口分析

1. 目标接口定位

打开某程旅行小程序,进入「酒店」板块,选择某一城市(如北京),筛选入住 / 离店日期,触发数据加载。此时在 Charles 中可捕获到对应的酒店列表接口,特征如下:

  • 请求方式:POST(部分接口为 GET);
  • 请求域名:多为某程官方域名(如<font style="color:rgb(31, 35, 41);background-color:rgba(0, 0, 0, 0);">api.ctrip.com</font>,实际需以抓包结果为准);
  • 请求头:包含<font style="color:rgb(31, 35, 41);background-color:rgba(0, 0, 0, 0);">User-Agent</font>(小程序专属 UA,格式如<font style="color:rgb(31, 35, 41);background-color:rgba(0, 0, 0, 0);">Mozilla/5.0 (iPhone; CPU iPhone OS 16_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148 MicroMessenger/8.0.30(0x18001e31) NetType/WIFI MiniProgramEnv/Windows WindowsWechat/3.9.5.81</font>)、<font style="color:rgb(31, 35, 41);background-color:rgba(0, 0, 0, 0);">Referer</font><font style="color:rgb(31, 35, 41);background-color:rgba(0, 0, 0, 0);">Content-Type</font>(application/json)等;
  • 请求参数:包含<font style="color:rgb(31, 35, 41);background-color:rgba(0, 0, 0, 0);">cityId</font>(城市 ID)、<font style="color:rgb(31, 35, 41);background-color:rgba(0, 0, 0, 0);">checkInDate</font>(入住日期)、<font style="color:rgb(31, 35, 41);background-color:rgba(0, 0, 0, 0);">checkOutDate</font>(离店日期)、<font style="color:rgb(31, 35, 41);background-color:rgba(0, 0, 0, 0);">pageNum</font>(页码)、<font style="color:rgb(31, 35, 41);background-color:rgba(0, 0, 0, 0);">pageSize</font>(每页条数)、<font style="color:rgb(31, 35, 41);background-color:rgba(0, 0, 0, 0);">sign</font>(签名参数)、<font style="color:rgb(31, 35, 41);background-color:rgba(0, 0, 0, 0);">timestamp</font>(时间戳)等;
  • 响应数据:JSON 格式,包含酒店 ID、酒店名称、价格、评分、地址等核心字段,无复杂加密(部分接口可能对响应数据进行 AES 加密,需进一步解析)。

2. 关键参数解析

本次实战以未加密的公开接口(模拟场景,贴合技术学习需求)为例,核心参数说明:

参数名 含义 取值示例
cityId 城市 ID 1(北京)
checkInDate 入住日期 2026-01-15
checkOutDate 离店日期 2026-01-18
pageNum 页码 1
pageSize 每页条数 20
timestamp 时间戳(毫秒级) 1736985600000
sign 签名(简化版,实际为参数拼接加密) md5(cityId+checkInDate+timestamp+secret)

其中,<font style="color:rgb(31, 35, 41);background-color:rgba(0, 0, 0, 0);">sign</font>参数是小程序接口的核心反爬手段之一,通常由平台后端约定的 secret 密钥 + 请求参数拼接后进行 MD5/SHA256 加密生成。本次实战中,我们模拟简化版签名生成逻辑(实际场景需通过逆向工程解析小程序源码获取完整签名逻辑)。

四、实战:某程旅行小程序酒店数据爬虫开发

1. 爬虫核心流程设计

  1. 定义配置参数(城市 ID、日期、页码、请求域名等);
  2. 实现签名参数生成逻辑(模拟 MD5 加密);
  3. 伪装请求头,构造完整请求参数;
  4. 发送 HTTP 请求,获取响应数据;
  5. 解析 JSON 响应,提取核心酒店数据;
  6. 数据清洗与存储(保存为 CSV 文件);
  7. 实现多页数据抓取,添加请求延迟,规避反爬。

2. 完整实现代码

python

运行

plain 复制代码
import requests
import pandas as pd
import time
import hashlib
from fake_useragent import UserAgent
from typing import List, Dict

class CtripMiniProgramSpider:
    """
    某程旅行小程序酒店数据爬虫(仅用于技术学习)
    """
    def __init__(self):
        # 基础配置
        self.base_url = "https://api.ctrip.com/hotel/list"  # 模拟接口,实际以抓包为准
        self.city_id = "1"  # 北京城市ID
        self.check_in_date = "2026-01-15"  # 入住日期
        self.check_out_date = "2026-01-18"  # 离店日期
        self.page_size = 20  # 每页条数
        self.max_page = 5  # 最大抓取页码
        self.secret = "ctrip_mini_program_secret_123"  # 模拟secret密钥,实际需逆向获取
        self.session = requests.Session()  # 维持会话状态
        self.ua = UserAgent()  # 随机生成UA
        self.all_hotel_data: List[Dict] = []  # 存储所有酒店数据

    def generate_sign(self, params: Dict) -> str:
        """
        生成签名参数(模拟MD5加密逻辑,实际需贴合平台真实算法)
        :param params: 核心请求参数
        :return: 加密后的签名字符串
        """
        # 1. 按平台约定的顺序拼接参数(关键:参数顺序必须与小程序一致)
        sign_str = f"{params['cityId']}{params['checkInDate']}{params['checkOutDate']}{params['timestamp']}{self.secret}"
        # 2. MD5加密
        md5_obj = hashlib.md5()
        md5_obj.update(sign_str.encode("utf-8"))
        return md5_obj.hexdigest().upper()  # 转为大写,贴合多数平台签名格式

    def get_request_params(self, page_num: int) -> Dict:
        """
        构造完整的请求参数
        :param page_num: 页码
        :return: 完整请求参数
        """
        # 1. 生成毫秒级时间戳
        timestamp = int(time.time() * 1000)
        # 2. 核心参数(无签名)
        core_params = {
            "cityId": self.city_id,
            "checkInDate": self.check_in_date,
            "checkOutDate": self.check_out_date,
            "pageNum": page_num,
            "pageSize": self.page_size,
            "timestamp": timestamp
        }
        # 3. 生成签名并添加到参数中
        core_params["sign"] = self.generate_sign(core_params)
        return core_params

    def get_request_headers(self) -> Dict:
        """
        构造请求头,伪装小程序请求
        :return: 请求头字典
        """
        return {
            "User-Agent": self.ua.random,  # 随机UA
            "Content-Type": "application/json; charset=utf-8",
            "Referer": "https://servicewechat.com/wx1234567890abcdef/0/page-frame.html",  # 模拟小程序Referer
            "Accept": "application/json, text/plain, */*",
            "X-Requested-With": "XMLHttpRequest"
        }

    def parse_hotel_data(self, response_json: Dict) -> None:
        """
        解析响应数据,提取核心酒店信息
        :param response_json: 接口返回的JSON数据
        """
        try:
            # 模拟响应数据结构,实际以抓包的接口响应为准
            hotel_list = response_json.get("data", {}).get("hotelList", [])
            if not hotel_list:
                print("当前页码无酒店数据")
                return
            # 提取核心字段
            for hotel in hotel_list:
                hotel_info = {
                    "hotel_id": hotel.get("hotelId", ""),
                    "hotel_name": hotel.get("hotelName", ""),
                    "price": hotel.get("lowestPrice", 0),
                    "score": hotel.get("hotelScore", 0.0),
                    "address": hotel.get("address", ""),
                    "star_rating": hotel.get("starRating", ""),
                    "check_in_date": self.check_in_date,
                    "check_out_date": self.check_out_date
                }
                self.all_hotel_data.append(hotel_info)
            print(f"成功解析{len(hotel_list)}条酒店数据")
        except Exception as e:
            print(f"数据解析失败:{str(e)}")

    def crawl_single_page(self, page_num: int) -> bool:
        """
        抓取单页酒店数据
        :param page_num: 页码
        :return: 抓取是否成功(True/False)
        """
        try:
            # 1. 构造请求参数和请求头
            params = self.get_request_params(page_num)
            headers = self.get_request_headers()
            # 2. 发送POST请求(模拟接口,实际需根据抓包结果调整请求方式)
            response = self.session.post(
                url=self.base_url,
                json=params,
                headers=headers,
                timeout=10
            )
            # 3. 状态码校验
            response.raise_for_status()
            # 4. 解析JSON响应
            response_json = response.json()
            # 5. 校验接口返回是否成功(模拟平台返回码,实际以抓包为准)
            if response_json.get("code") != 200:
                print(f"接口返回失败,错误信息:{response_json.get('msg')}")
                return False
            # 6. 解析酒店数据
            self.parse_hotel_data(response_json)
            # 7. 添加请求延迟,规避反爬(关键:控制请求频率)
            time.sleep(2)
            return True
        except requests.exceptions.RequestException as e:
            print(f"单页抓取失败(页码:{page_num}),错误信息:{str(e)}")
            return False

    def crawl_all_pages(self) -> None:
        """
        抓取所有页码的酒店数据
        """
        print("开始抓取某程旅行小程序酒店数据...")
        for page_num in range(1, self.max_page + 1):
            print(f"正在抓取第{page_num}页数据...")
            success = self.crawl_single_page(page_num)
            if not success:
                print(f"第{page_num}页抓取失败,跳过该页")
                continue
        print(f"所有页码抓取完成,共获取{len(self.all_hotel_data)}条酒店数据")

    def save_data_to_csv(self, file_path: str = "ctrip_hotel_data.csv") -> None:
        """
        将抓取的数据保存为CSV文件
        :param file_path: 保存文件路径
        """
        try:
            if not self.all_hotel_data:
                print("无有效数据可保存")
                return
            # 转换为DataFrame
            df = pd.DataFrame(self.all_hotel_data)
            # 保存为CSV文件(忽略索引,编码为utf-8-sig防止中文乱码)
            df.to_csv(file_path, index=False, encoding="utf-8-sig")
            print(f"数据已成功保存至:{file_path}")
        except Exception as e:
            print(f"数据保存失败:{str(e)}")

if __name__ == "__main__":
    # 初始化爬虫并执行抓取
    spider = CtripMiniProgramSpider()
    # 抓取所有页码数据
    spider.crawl_all_pages()
    # 保存数据到CSV
    spider.save_data_to_csv()

3. 代码关键说明

  1. 会话维持 :使用<font style="color:rgb(31, 35, 41);background-color:rgba(0, 0, 0, 0);">requests.Session()</font>维持会话状态,避免每次请求重新建立连接,同时可自动保存 Cookie,适配需要登录验证的接口;
  2. 签名生成:模拟 MD5 加密逻辑,实际场景中需通过反编译小程序(如使用 Jadx、Frida 工具)获取真实的签名参数拼接顺序和加密算法;
  3. 反反爬策略 :使用<font style="color:rgb(31, 35, 41);background-color:rgba(0, 0, 0, 0);">fake-useragent</font>生成随机 UA,添加<font style="color:rgb(31, 35, 41);background-color:rgba(0, 0, 0, 0);">time.sleep(2)</font>控制请求频率,避免高频请求触发平台风控,使用高匿代理IP避免引起反爬,例如亿牛云隧道代理
  4. 数据存储 :使用<font style="color:rgb(31, 35, 41);background-color:rgba(0, 0, 0, 0);">pandas</font>将数据保存为 CSV 文件,指定<font style="color:rgb(31, 35, 41);background-color:rgba(0, 0, 0, 0);">encoding="utf-8-sig"</font>防止中文乱码,方便后续数据分析;
  5. 异常处理 :添加<font style="color:rgb(31, 35, 41);background-color:rgba(0, 0, 0, 0);">try-except</font>捕获请求异常和数据解析异常,提高爬虫的稳定性。

4. 运行结果与后续优化

  1. 运行结果 :执行代码后,控制台会输出各页码的抓取状态,最终在当前目录生成<font style="color:rgb(31, 35, 41);background-color:rgba(0, 0, 0, 0);">ctrip_hotel_data.csv</font>文件,包含酒店 ID、名称、价格等核心字段,可直接用 Excel 打开查看;
  2. 后续优化方向
    • 逆向解析真实签名逻辑:使用 FridaHook 小程序的加密函数,获取完整的<font style="color:rgb(31, 35, 41);background-color:rgba(0, 0, 0, 0);">sign</font>生成算法;
    • 处理加密响应数据:若接口响应为 AES 加密,添加<font style="color:rgb(31, 35, 41);background-color:rgba(0, 0, 0, 0);">pycryptodome</font>的解密逻辑,解析真实数据;
    • 实现异步抓取:使用<font style="color:rgb(31, 35, 41);background-color:rgba(0, 0, 0, 0);">aiohttp</font>替代<font style="color:rgb(31, 35, 41);background-color:rgba(0, 0, 0, 0);">requests</font>,提高多页数据的抓取效率;
    • 增加数据去重:针对<font style="color:rgb(31, 35, 41);background-color:rgba(0, 0, 0, 0);">hotel_id</font>进行去重,避免重复抓取相同酒店数据;
    • 完善日志记录:使用<font style="color:rgb(31, 35, 41);background-color:rgba(0, 0, 0, 0);">logging</font>模块替代<font style="color:rgb(31, 35, 41);background-color:rgba(0, 0, 0, 0);">print</font>,方便排查抓取过程中的问题。

五、总结与风险提示

1. 核心总结

本文围绕某程旅行小程序爬虫展开,从技术差异、前期准备、接口分析到实战开发,完整呈现了小程序爬虫的落地流程。核心要点如下:

  1. 小程序爬虫的核心在于抓包配置与接口参数解析,尤其是签名等加密参数的模拟;
  2. 合规性是爬虫开发的前提,不得触碰平台数据安全与服务器稳定的红线;
  3. 反反爬策略的核心是 "伪装" 与 "节制",模拟正常用户请求,控制请求频率;
  4. 实战代码仅为技术演示,实际落地需根据平台接口的真实情况进行调整与优化。
相关推荐
程序员agions2 小时前
Node.js 爬虫实战指南(四):反反爬策略大全,和网站斗智斗勇
爬虫·node.js
小小王app小程序开发2 小时前
家政服务小程序特殊玩法开发全解析:技术实现+架构支撑+合规落地
小程序·架构
游戏开发爱好者82 小时前
2025年iOS应用上架App Store全指南,开发者必看
android·ios·小程序·https·uni-app·iphone·webview
程序员agions3 小时前
Node.js 爬虫实战指南(二):动态页面爬取,Puppeteer 大显身手
爬虫·node.js
集成显卡3 小时前
CVE检索工具 | 开发一款检索漏洞信息的小程序
网络安全·小程序·uni-app·cve·漏洞信息
luffy54593 小时前
微信小程序实现图片横向滑动的示例
微信小程序·小程序
嫂子的姐夫3 小时前
017-续集-贝壳登录(剩余三个参数)
爬虫·python·逆向
万岳软件开发小城3 小时前
直播电商系统源码搭建直播带货APP/小程序的完整流程
小程序·php·软件开发·直播带货系统源码·直播电商app开发
棒棒的唐4 小时前
使用微信小程序版Vant的upload组件上传身份证的样式自定义方案(Css魔改版)
css·微信小程序·小程序