ASFEnhance HTML解析技巧:HtmlParser处理复杂页面
在ASFEnhance插件开发中,HTML解析是处理Steam社区页面、商店数据和账户相关信息的核心能力。本文将系统介绍如何使用项目内置的HtmlParser类高效处理各类复杂HTML页面,通过实例解析常见场景的解决方案。
HtmlParser模块架构
ASFEnhance采用模块化设计,将不同业务场景的HTML解析逻辑分离到独立文件中,主要包括:
- 账户相关解析:ASFEnhance/Account/HtmlParser.cs
- 好友系统解析:ASFEnhance/Friend/HtmlParser.cs
- 个人资料解析:ASFEnhance/Profile/HtmlParser.cs
- 商店页面解析:ASFEnhance/Store/HtmlParser.cs
- 购物车解析:ASFEnhance/Cart/HtmlParser.cs
这种架构确保了解析逻辑与业务功能的紧密耦合,同时保持代码可维护性。以账户历史记录解析为例,典型调用流程如下:
// 从Web请求获取HTML内容
var response = await WebRequest.GetAccountHistoryPage(...);
// 解析分页游标数据
var cursor = HtmlParser.ParseCursorData(response);
// 解析交易记录
var historyData = HtmlParser.ParseHistory(tableElement, exchangeRates, defaultCurrency);
核心解析技术与实战
1. 结构化数据提取
Steam页面通常包含大量嵌套HTML结构,HtmlParser使用AngleSharp库提供的CSS选择器进行精确定位。以解析账户邮箱为例:
internal static string? ParseAccountEmail(IDocument? document)
{
if (document == null) return null;
// 使用多层CSS选择器定位目标元素
var eleEmail = document.QuerySelector(
"#main_content div.account_setting_sub_block:nth-child(1) > div:nth-child(2) span.account_data_field");
return eleEmail?.TextContent;
}
关键技巧:
- 使用浏览器开发者工具复制精确CSS选择器
- 避免过度依赖页面结构,优先使用具有唯一ID的容器元素
- 对可能为null的元素进行安全检查
2. 复杂表格数据处理
账户交易历史页面采用动态加载的表格结构,ParseHistory方法展示了如何处理这种复杂数据:
internal static HistoryParseResponse ParseHistory(IElement tableElement,
Dictionary<string, decimal> currencyRates, string defaultCurrency)
{
var rows = tableElement.QuerySelectorAll("tr");
var result = new HistoryParseResponse();
foreach (var row in rows)
{
// 提取表格单元格数据
var strItem = row.QuerySelector("td.wht_items")?.Text().Trim() ?? "";
var strType = row.QuerySelector("td.wht_type")?.Text().Trim() ?? "";
var strTotal = row.QuerySelector("td.wht_total")?.Text().Trim() ?? "";
// 根据交易类型分类处理
if (strType.StartsWith("购买"))
{
result.StorePurchase += ParseMoneyString(strTotal);
}
// ...其他交易类型处理
}
return result;
}
表格解析最佳实践:
- 先定位表格主体(
<tbody>)而非整个表格 - 使用语义化类名筛选有效行(如
wht_row) - 对数值型数据进行类型转换和异常处理
3. 货币与数值处理
Steam支持多币种显示,HtmlParser实现了智能货币识别系统,解决符号歧义(如¥同时表示货币A和货币B):
// 货币符号解析逻辑
string ParseSymbol(string symbol1, string symbol2)
{
if (symbol1.Contains('¥') || symbol2.Contains('¥'))
{
// 使用钱包默认货币区分货币A/货币B
return defaultCurrency;
}
// ...其他货币处理
}
// 数值解析
decimal ParseMoneyString(string strMoney)
{
var match = RegexUtils.MatchHistoryItem().Match(strMoney);
if (!match.Success) return 0;
var negative = match.Groups[1].Value == "-";
var strPrice = match.Groups[3].Value;
// 根据货币类型选择正确的数字格式
var numberFormat = DotCurrency.Contains(currency)
? new NumberFormatInfo { CurrencyDecimalSeparator = "." }
: new NumberFormatInfo { CurrencyDecimalSeparator = "," };
return decimal.TryParse(strPrice, NumberStyles.Currency, numberFormat, out var price)
? (negative ? -price : price)
: 0;
}
关键挑战:
- 不同地区数字格式(1,000.00 vs 1.000,00)
- 货币符号位置差异($100 vs 100€)
- 退款和负数交易的特殊标记
4. 动态内容提取
对于使用JavaScript动态加载的内容(如无限滚动列表),HtmlParser结合正则表达式提取内嵌JSON数据:
internal static AccountHistoryResponse.CursorData? ParseCursorData(HtmlDocumentResponse? response)
{
if (response?.Content?.Body == null) return null;
var content = response.Content.Body.InnerHtml;
// 使用正则匹配内嵌JSON数据
var match = RegexUtils.MatchHistoryCursor().Match(content);
if (!match.Success) return null;
try
{
// 直接反序列化为对象
return match.Groups[1].Value.ToJsonObject<AccountHistoryResponse.CursorData>();
}
catch
{
return null;
}
}
常见问题与解决方案
1. 页面结构变化应对
Steam页面布局可能不定期更新,导致解析失败。防御性编程策略包括:
// 安全的元素查询链
var container = document.QuerySelector("#content_container");
if (container == null)
{
ASFLogger.LogGenericWarning("内容容器未找到");
return null;
}
var dataRows = container.QuerySelectorAll("div.data_row");
if (dataRows.Length == 0)
{
// 尝试备选选择器
dataRows = container.QuerySelectorAll("div.alt_data_row");
}
2. 性能优化
解析大型HTML文档时,可通过以下方式提升性能:
- 局部解析:只提取文档中需要的部分
- 延迟加载:分页数据按需解析
- 缓存复用:重复使用AngleSharp解析上下文
// 只解析表格部分而非整个文档
var tableHtml = response.Content.QuerySelector("table#transaction_history")?.OuterHtml;
if (tableHtml != null)
{
using var context = BrowsingContext.New(Configuration.Default);
var tableDocument = await context.OpenAsync(req => req.Content(tableHtml));
// 仅解析表格内容
return ParseHistory(tableDocument.Body, ...);
}
3. 调试与日志
解析过程中加入详细日志,便于问题诊断:
if (string.IsNullOrEmpty(currency))
{
ASFLogger.LogGenericWarning($"检测货币符号失败, 使用默认货币单位 {defaultCurrency}");
return defaultCurrency;
}
高级应用:构建解析器抽象层
对于复杂场景,可以设计解析器接口实现策略模式:
// 解析器接口定义
interface IHtmlParser<T>
{
T Parse(IDocument document);
}
// 账户解析器实现
class AccountParser : IHtmlParser<AccountData>
{
public AccountData Parse(IDocument document)
{
// 实现具体解析逻辑
}
}
// 使用依赖注入
var parser = new AccountParser();
var data = parser.Parse(document);
这种模式便于单元测试和解析逻辑替换。
总结与最佳实践
ASFEnhance的HtmlParser模块展示了如何在实际项目中高效处理复杂HTML解析任务。核心要点包括:
- 模块化设计:按业务场景分离解析逻辑
- 多技术结合:CSS选择器+正则表达式+JSON反序列化
- 防御性编程:处理null值、格式异常和结构变化
- 性能优化:局部解析和延迟加载
- 详细日志:便于问题诊断和后期维护
通过本文介绍的技术和实例,开发者可以快速掌握复杂HTML页面的解析技巧,提升ASFEnhance插件的数据处理能力。更多解析实例可参考项目源代码中的各HtmlParser.cs文件。
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考



