无需模拟登录:直接调用问财网移动端API接口

236 阅读7分钟

一、原理分析:为何选择移动端API?

与Web端相比,移动端(APP)的API通常设计得更加简洁、清晰和稳定。出于性能和省流量的考虑,移动端API通常以结构化数据(如JSON)的形式返回数据,而不是大量的HTML代码。这正好为我们数据提取提供了极大的便利。

我们的核心思路是:

  1. 抓包分析:使用抓包工具捕获问财网APP(或其官方网站发出的)的HTTP请求。
  2. 识别接口:从众多请求中筛选出负责核心数据查询的API。
  3. 参数解析:分析该API请求的URL、参数(Params)、请求头(Headers)的含义,找出哪些是固定值,哪些是可变值。
  4. 模拟请求:使用Python的<font style="color:rgb(15, 17, 21);background-color:rgb(235, 238, 242);">requests</font>库,完全模拟一个合法的客户端请求,直接获取JSON数据。

这种方法避免了解析HTML的麻烦,也无需处理复杂的JavaScript逻辑和Cookie会话维持,只要一次分析成功,便可长期稳定使用。

二、实战步骤:逆向分析与接口调用

步骤一:抓包与接口识别

首先,你需要一个抓包工具。FiddlerCharlesBurp Suite 都是优秀的选择。本文以Charles为例。

  1. 在你的电脑上设置好抓包代理。
  2. 让你的手机和电脑处于同一局域网,并在手机上设置代理指向你的电脑。
  3. 打开问财APP,输入一个查询问题,例如“主板上市的科技股”。
  4. 观察Charles中捕获的HTTP请求流。

你会发现一个非常“显眼”的请求,其URL可能包含<font style="color:rgb(15, 17, 21);background-color:rgb(235, 238, 242);">search</font><font style="color:rgb(15, 17, 21);background-color:rgb(235, 238, 242);">stock_list</font>等关键字,并且Response是清晰的JSON格式。经过分析,问财的核心查询接口通常类似于:\ <font style="color:rgb(15, 17, 21);background-color:rgb(235, 238, 242);">https://www.iwencai.com/unifiedwap/unified-wap/v2/result/get-robot-data</font>

步骤二:关键参数分析

这是最关键的一步。我们需要模拟一个合法的请求,就必须理解每个参数的意义。通过对比多次不同查询的请求,我们可以分析出以下核心参数:

  • **<font style="color:rgb(15, 17, 21);background-color:rgb(235, 238, 242);">question</font>**: 这是最重要的参数,就是你想要查询的自然语言问题,例如“主板上市的科技股”。
  • **<font style="color:rgb(15, 17, 21);background-color:rgb(235, 238, 242);">perpage</font>**: 每页返回的数据条数,可以设置得大一些以一次性获取所有数据。
  • **<font style="color:rgb(15, 17, 21);background-color:rgb(235, 238, 242);">page</font>**: 页码,通常配合<font style="color:rgb(15, 17, 21);background-color:rgb(235, 238, 242);">perpage</font>使用进行分页。
  • **<font style="color:rgb(15, 17, 21);background-color:rgb(235, 238, 242);">source</font>**: 数据来源,通常是<font style="color:rgb(15, 17, 21);background-color:rgb(235, 238, 242);">Ths_iwencai_Xuangu</font>(同花顺问财选股)。
  • **<font style="color:rgb(15, 17, 21);background-color:rgb(235, 238, 242);">secondary_intent</font>**: 二级意图,股票相关查询通常为<font style="color:rgb(15, 17, 21);background-color:rgb(235, 238, 242);">stock</font>
  • **<font style="color:rgb(15, 17, 21);background-color:rgb(235, 238, 242);">log_info</font>**: 一个包含查询上下文信息的JSON字符串,通常可以固定不变或微调。
  • **<font style="color:rgb(15, 17, 21);background-color:rgb(235, 238, 242);">rsh</font>**: 一个看起来像MD5哈希值的参数,用于校验。这是难点所在。它很可能由其他参数(如<font style="color:rgb(15, 17, 21);background-color:rgb(235, 238, 242);">question</font>)和一个固定的密钥(Salt)通过某种算法生成。需要逆向APP的代码才能找到算法。但幸运的是,有时这个参数在Web端是固定的或可预测的。

一个重要发现:在问财网的网页版中,通过浏览器开发者工具(F12-Network)进行分析,有时可以找到无需动态计算<font style="color:rgb(15, 17, 21);background-color:rgb(235, 238, 242);">rsh</font>参数的请求方式。请求头中的<font style="color:rgb(15, 17, 21);background-color:rgb(235, 238, 242);">Hexin-V</font>(同花顺的校验Token)可能是另一个难点,但同样存在一些方法可以绕过或生成。

经过社区多次实践,发现问财网的接口校验有时并不严格。直接使用Web端捕获的固定参数,往往也能成功请求。这意味着我们可以“借用”一套有效的参数,只修改<font style="color:rgb(15, 17, 21);background-color:rgb(235, 238, 242);">question</font>和其他必要字段。

步骤三:Python代码实现

基于以上分析,我们可以构造HTTP请求。核心是构造一个看起来完全来自官方客户端(无论是APP还是浏览器)的请求。

以下是一个成功的Python示例代码。请注意,其中的**<font style="color:rgb(15, 17, 21);background-color:rgb(235, 238, 242);">hexin-v</font>****<font style="color:rgb(15, 17, 21);background-color:rgb(235, 238, 242);">rsh</font>**值可能会在一段时间后失效,如果失效,你需要按照步骤一和二的方法,从你自己的浏览器中捕获最新的有效值。

import requests
import json
import pandas as pd

# 代理配置信息
proxyHost = "www.16yun.cn"
proxyPort = "5445"
proxyUser = "16QMSOML"
proxyPass = "280651"

# 构造代理字典
proxies = {
    "http": f"http://{proxyUser}:{proxyPass}@{proxyHost}:{proxyPort}",
    "https": f"http://{proxyUser}:{proxyPass}@{proxyHost}:{proxyPort}"
}

def fetch_iwencai_data(question):
    """
    直接调用问财网API接口获取数据
    :param question: 要查询的问财问题
    :return: 包含查询结果的Pandas DataFrame
    """
    # 1. 目标API URL
    url = "https://www.iwencai.com/unifiedwap/unified-wap/v2/result/get-robot-data"

    # 2. 构造请求头 (Headers)
    # 注意:这里的Hexin-V值非常重要且会过期,需要你自己从浏览器请求中复制替换!
    headers = {
        'Host': 'www.iwencai.com',
        'Connection': 'keep-alive',
        'Content-Length': '0',
        'Accept': 'application/json, text/plain, */*',
        'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/107.0.0.0 Safari/537.36',
        'Hexin-V': 'AwhUqB9fA7uJis6wUTQ9f6KqU9K5wK5k9pK5iE2kCjqJqJp9kC2iE5iE5pJ', # <- 替换为你自己的有效Token!
        'Content-Type': 'application/json',
        'Origin': 'https://www.iwencai.com',
        'Sec-Fetch-Site': 'same-origin',
        'Sec-Fetch-Mode': 'cors',
        'Sec-Fetch-Dest': 'empty',
        'Referer': 'https://www.iwencai.com/',
        'Accept-Encoding': 'gzip, deflate, br',
        'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8',
    }

    # 3. 构造查询参数 (Query Parameters)
    params = {
        'question': question,       # 核心:你的问题
        'perpage': '500',           # 一次获取500条,可根据需要调整
        'page': '1',
        'secondary_intent': 'stock',
        'source': 'Ths_iwencai_Xuangu',
        'version': '2.0',
        'query_area': '',
        'block_list': '',
        'add_info': '',
        'rsh': 'Ths_iwencai_Xuangu_7cprh7qj700q7qj7', # <- 这个值也可能需要替换!
        # 注意:log_info通常是一个JSON字符串,这里为了简洁已简化。
        # 在实际复杂场景中,可能需要根据问题动态生成或捕获一个固定的有效值。
        'log_info': '{"input_type":"typewrite"}',
    }

    try:
        # 4. 发送GET请求(添加proxies参数)
        # 注意:这里使用的是params参数,requests库会将其自动拼接为URL后的查询字符串
        response = requests.get(url, headers=headers, params=params, proxies=proxies, timeout=30)
        
        # 5. 检查请求是否成功
        if response.status_code == 200:
            print("请求成功!")
            data_json = response.json() # 解析JSON响应
            # 6. 提取核心数据
            # JSON结构需要具体分析,通常数据在 data -> answer -> components -> [0] -> data -> meta -> extra -> pick
            try:
                # 这个路径需要根据实际的JSON返回结构进行调整!
                stock_list = data_json['data']['answer']['components'][0]['data']['meta']['extra']['pick']
                # 将数据转换为Pandas DataFrame
                df = pd.DataFrame(stock_list)
                return df
            except KeyError as e:
                print(f"解析JSON数据时出错,键错误: {e}")
                print("完整的响应JSON:", json.dumps(data_json, indent=2, ensure_ascii=False))
                return None
        else:
            print(f"请求失败,状态码: {response.status_code}")
            print(response.text)
            return None
            
    except requests.exceptions.ProxyError as e:
        print(f"代理连接错误: {e}")
        return None
    except requests.exceptions.ConnectTimeout as e:
        print(f"连接超时: {e}")
        return None
    except requests.exceptions.RequestException as e:
        print(f"网络请求异常: {e}")
        return None

# 使用示例
if __name__ == '__main__':
    search_question = "主板上市的科技股,2023年净利润增长大于20%"  # 你的问财问题
    result_df = fetch_iwencai_data(search_question)

    if result_df is not None:
        print(f"共获取到 {len(result_df)} 条数据")
        # 打印前几行
        print(result_df.head())
        # 保存到CSV文件
        result_df.to_csv('iwencai_stock_data.csv', index=False, encoding='utf-8-sig')
        print("数据已保存到 iwencai_stock_data.csv")
    else:
        print("未获取到数据")

三、注意事项与最佳实践

  1. 参数失效:代码中的<font style="color:rgb(15, 17, 21);background-color:rgb(235, 238, 242);">Hexin-V</font><font style="color:rgb(15, 17, 21);background-color:rgb(235, 238, 242);">rsh</font>值是最可能失效的部分。你需要使用浏览器开发者工具,访问问财官网(www.iwencai.com),进行一次查询,在“网络(Network)”面板中找到<font style="color:rgb(15, 17, 21);background-color:rgb(235, 238, 242);">get-robot-data</font>请求,从中复制最新的<font style="color:rgb(15, 17, 21);background-color:rgb(235, 238, 242);">Hexin-V</font>请求头和<font style="color:rgb(15, 17, 21);background-color:rgb(235, 238, 242);">rsh</font>参数值替换到代码中。
  2. 频率限制:即使直接调用API,也应保持合理的请求频率,避免过于频繁的请求导致IP被暂时封锁。建议在循环中添加<font style="color:rgb(15, 17, 21);background-color:rgb(235, 238, 242);">time.sleep()</font>进行延时。
  3. 错误处理:代码中只做了基本的错误处理。在生产环境中,应增加更完善的异常捕获、重试机制等。
  4. 数据结构变化:问财可能会调整API返回的JSON数据结构。如果解析失败(出现<font style="color:rgb(15, 17, 21);background-color:rgb(235, 238, 242);">KeyError</font>),应重新分析JSON结构,并调整数据提取的代码路径。
  5. 合法合规:请将获取的数据用于个人学习和研究,遵守问财网的用户协议,尊重数据的版权和知识产权。