Android端Java提取Yahoo财经HTML表格数据问题求助
Android端Java提取Yahoo财经财务表格数据方案
问题说明
需提取Yahoo财经某公司(如AAPL)财务板块的表格数据,目标是获取特定值(如2022年9月30日的Total Revenue:394,328,000),并将完整表格转为Map结构。现有代码因页面数据结构变化(原Root.App.main段变为加密/混淆字符串)返回空值,需调整实现方案。
核心问题分析
- 页面数据结构变更:Yahoo财经更新了页面渲染逻辑,原
root.App.main变量的存储形式或正则匹配规则失效,导致无法提取有效JSON数据。 - 原代码缺陷:存在重复变量定义(如
requestURL、tableNames),且数据解析路径默认指向季度数据,与目标年度数据不匹配。
解决方案步骤
1. 修正数据提取正则
调整正则表达式,适配Yahoo最新的页面结构,匹配存储财务数据的script标签内容:
// 适配压缩/混淆后的格式,精准匹配root.App.main数据段 String re = "root\\.App\\.main\\s*=\\s*(.*?);\\s*\\}\\(this\\)\\)\\s*;";
若上述正则无法匹配,需查看页面源码确认当前存储数据的变量名(如window.__INITIAL_STATE__),再调整正则规则。
2. 处理压缩/混淆数据
提取到的JSON字符串可能存在压缩或转义字符,直接转换为JSONObject即可自动处理,无需额外解密(Yahoo当前仅做代码压缩,未加密核心数据)。
3. 修正数据解析路径
若目标为年度财务数据,将解析路径从季度数据切换为年度数据:
private JSONArray getTable(JSONObject json) throws JSONException { return json.getJSONObject("context") .getJSONObject("dispatcher") .getJSONObject("stores") .getJSONObject("QuoteSummaryStore") .getJSONObject("incomeStatementHistory") // 替换原quarterly路径 .getJSONArray("incomeStatementHistory"); }
4. 转换为目标Map结构
将JSON数据转换为Map<String, Map<String, String>>,外层key为日期,内层key为财务项名称,value为对应数值:
private Map<String, Map<String, String>> convertToMap(JSONArray table, Map<String, Map<String, String>> tableNames) throws JSONException { Map<String, Map<String, String>> result = new LinkedHashMap<>(); String[] dates = getDates(table); // 初始化每个日期的财务项Map for (String date : dates) { result.put(date, new LinkedHashMap<>()); } // 填充每个财务项的数值 for (Map.Entry<String, Map<String, String>> categoryEntry : tableNames.entrySet()) { for (Map.Entry<String, String> itemEntry : categoryEntry.getValue().entrySet()) { String itemName = itemEntry.getKey(); String itemKey = itemEntry.getValue(); String[] values = getRow(table, itemKey); for (int i = 0; i < dates.length; i++) { result.get(dates[i]).put(itemName, values[i]); } } } return result; }
完整修正代码
import org.jsoup.Jsoup; import org.jsoup.nodes.Document; import org.jsoup.nodes.Element; import org.jsoup.select.Elements; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; import java.util.*; import android.text.TextUtils; import android.util.Log; import java.util.regex.Matcher; import java.util.regex.Pattern; public class YahooFinanceExtractor { private static final String REQUEST_URL = "https://finance.yahoo.com/quote/AAPL/financials?p=AAPL"; private static final String USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/118.0.0.0 Safari/537.36"; public void extractFinancialData() { try { Document doc = Jsoup.connect(REQUEST_URL).userAgent(USER_AGENT).get(); Elements scriptTags = doc.getElementsByTag("script"); String re = "root\\.App\\.main\\s*=\\s*(.*?);\\s*\\}\\(this\\)\\)\\s*;"; String data = null; for (Element script : scriptTags) { Pattern pattern = Pattern.compile(re, Pattern.DOTALL); Matcher matcher = pattern.matcher(script.html()); if (matcher.find()) { data = matcher.group(1); break; } } if (data == null) { Log.e("err", "未找到目标数据段"); return; } JSONObject jo = new JSONObject(data); JSONArray table = getTable(jo); Map<String, Map<String, String>> tableNames = getTableNames(); Map<String, Map<String, String>> financialMap = convertToMap(table, tableNames); // 示例:获取2022年9月30日的Total Revenue String targetDate = "2022-09-30"; if (financialMap.containsKey(targetDate)) { String totalRevenue = financialMap.get(targetDate).get("Total Revenue"); Log.d("result", String.format("%s的Total Revenue: %s", targetDate, totalRevenue)); } // 打印完整Map数据 for (Map.Entry<String, Map<String, String>> entry : financialMap.entrySet()) { Log.d("full_data", String.format("日期: %s, 数据: %s", entry.getKey(), entry.getValue().toString())); } } catch (Exception e) { Log.e("err", "提取失败", e); } } private JSONArray getTable(JSONObject json) throws JSONException { return json.getJSONObject("context") .getJSONObject("dispatcher") .getJSONObject("stores") .getJSONObject("QuoteSummaryStore") .getJSONObject("incomeStatementHistory") .getJSONArray("incomeStatementHistory"); } private String[] getRow(JSONArray table, String name) throws JSONException { String[] values = new String[table.length()]; for (int i = 0; i < table.length(); i++) { JSONObject jo = table.getJSONObject(i); if (jo.has(name)) { jo = jo.getJSONObject(name); values[i] = jo.has("longFmt") ? jo.get("longFmt").toString() : "-"; } else { values[i] = "-"; } } return values; } private String[] getDates(JSONArray table) throws JSONException { String[] values = new String[table.length()]; for (int i = 0; i < table.length(); i++) { values[i] = table.getJSONObject(i).getJSONObject("endDate") .get("fmt").toString(); } return values; } private Map<String, Map<String, String>> getTableNames() { final Map<String, String> revenue = new LinkedHashMap<String, String>() {{ put("Total Revenue", "totalRevenue"); put("Cost of Revenue", "costOfRevenue"); put("Gross Profit", "grossProfit"); }}; final Map<String, String> operatingExpenses = new LinkedHashMap<String, String>() {{ put("Research Development", "researchDevelopment"); put("Selling General and Administrative", "sellingGeneralAdministrative"); put("Non Recurring", "nonRecurring"); put("Others", "otherOperatingExpenses"); put("Total Operating Expenses", "totalOperatingExpenses"); put("Operating Income or Loss", "operatingIncome"); }}; return new LinkedHashMap<String, Map<String, String>>() {{ put("Revenue", revenue); put("Operating Expenses", operatingExpenses); }}; } private Map<String, Map<String, String>> convertToMap(JSONArray table, Map<String, Map<String, String>> tableNames) throws JSONException { Map<String, Map<String, String>> result = new LinkedHashMap<>(); String[] dates = getDates(table); for (String date : dates) { result.put(date, new LinkedHashMap<>()); } for (Map.Entry<String, Map<String, String>> categoryEntry : tableNames.entrySet()) { for (Map.Entry<String, String> itemEntry : categoryEntry.getValue().entrySet()) { String itemName = itemEntry.getKey(); String itemKey = itemEntry.getValue(); String[] values = getRow(table, itemKey); for (int i = 0; i < dates.length; i++) { result.get(dates[i]).put(itemName, values[i]); } } } return result; } }
注意事项
- User-Agent适配:需使用符合现代浏览器的UA字符串,避免被Yahoo拦截。
- 数据结构变更:Yahoo财经可能随时调整页面数据结构,若再次失效需重新查看页面源码更新正则和解析路径。
- 异常处理:需增加空值判断和异常捕获,避免因数据缺失导致崩溃。
内容的提问来源于stack exchange,提问作者Zac1
相关产品推荐
相关产品推荐

