解决Competitive Companion解析AtCoder题目时的TypeError问题
问题背景与现象
在使用Competitive Companion浏览器扩展解析AtCoder平台题目时,部分用户遇到了TypeError错误。这个问题导致无法正确获取题目信息,影响了 competitive programming(程序设计竞赛)选手的日常训练效率。错误通常发生在以下场景:
- 打开AtCoder竞赛题目页面时
- 通过竞赛任务列表批量解析题目时
- 切换不同语言版本的题目描述时
技术原理分析
Competitive Companion工作流程
Competitive Companion解析题目的核心流程如下:
AtCoder解析器实现
AtCoder题目解析主要由AtCoderProblemParser类负责,其核心代码结构如下:
export class AtCoderProblemParser extends Parser {
public getMatchPatterns(): string[] {
return ['https://atcoder.jp/contests/*/tasks/*'];
}
public async parse(url: string, html: string): Promise<Sendable> {
const elem = htmlToElement(html);
const task = new TaskBuilder('AtCoder').setUrl(url);
// 提取题目名称
const name = [...elem.querySelector('h2, .h2').childNodes]
.filter(node => node.nodeType === Node.TEXT_NODE)
.map(node => node.textContent)
.join('')
.trim();
// 设置其他题目属性...
return task.build();
}
}
错误原因定位
经过代码分析,TypeError错误主要有以下两个可能原因:
1. DOM元素选择器问题
AtCoder网站可能更新了页面结构,导致解析器中的选择器无法找到目标元素:
// 风险代码
const name = [...elem.querySelector('h2, .h2').childNodes]
// 如果elem.querySelector('h2, .h2')返回null,将导致后续操作抛出TypeError
当页面结构变化导致querySelector返回null时,尝试访问childNodes属性就会抛出TypeError: Cannot read property 'childNodes' of null。
2. 匹配模式验证失败
扩展内部的URL匹配验证逻辑可能导致TypeError:
// 相关代码:match-pattern-to-reg-exp.ts
throw new TypeError(`"${pattern}" is not a valid MatchPattern`);
throw new TypeError(`"${pattern}" does not have a valid host`);
如果AtCoder的URL格式发生变化,可能导致匹配模式验证失败,从而抛出TypeError。
解决方案
方案一:增强DOM查询健壮性
修改AtCoderProblemParser.ts中的元素选择逻辑,增加空值检查:
// 原代码
const name = [...elem.querySelector('h2, .h2').childNodes]
.filter(node => node.nodeType === Node.TEXT_NODE)
.map(node => node.textContent)
.join('')
.trim();
// 修改后
const titleElement = elem.querySelector('h2, .h2');
if (!titleElement) {
throw new Error('无法找到标题元素');
}
const name = [...titleElement.childNodes]
.filter(node => node.nodeType === Node.TEXT_NODE)
.map(node => node.textContent?.trim() || '')
.join('')
.trim();
方案二:优化URL匹配模式
检查并更新AtCoderProblemParser的URL匹配模式:
// 修改getMatchPatterns方法
public getMatchPatterns(): string[] {
return [
'https://atcoder.jp/contests/*/tasks/*',
'https://atcoder.jp/contests/*/tasks/*?lang=*' // 新增对语言参数的支持
];
}
方案三:完善错误处理机制
在解析流程中增加try-catch块,提供更友好的错误提示:
public async parse(url: string, html: string): Promise<Sendable> {
try {
const elem = htmlToElement(html);
const task = new TaskBuilder('AtCoder').setUrl(url);
// 解析逻辑...
return task.build();
} catch (error) {
console.error(`解析AtCoder题目时出错: ${error.message}`, error);
// 提供错误恢复机制或备用解析方案
throw new Error(`解析失败: ${error.message}`);
}
}
实施步骤
1. 获取源码
git clone https://gitcode.com/gh_mirrors/co/competitive-companion
cd competitive-companion
2. 修改相关文件
src/parsers/problem/AtCoderProblemParser.ts
// 在parse方法中增加错误处理和空值检查
public async parse(url: string, html: string): Promise<Sendable> {
try {
const elem = htmlToElement(html);
const task = new TaskBuilder('AtCoder').setUrl(url);
// 提取题目名称(增强版)
const titleElement = elem.querySelector('h2, .h2');
if (!titleElement) {
throw new Error('无法找到标题元素');
}
const name = [...titleElement.childNodes]
.filter(node => node.nodeType === Node.TEXT_NODE)
.map(node => node.textContent?.trim() || '')
.join('')
.trim();
task.setName(name);
// 提取比赛类别(增强版)
const contestElement = elem.querySelector('.contest-name, .contest-title');
if (contestElement) {
task.setCategory(contestElement.textContent);
} else {
console.warn('无法找到比赛类别元素');
// 尝试从URL中提取比赛信息作为备选方案
const match = url.match(/contests\/([^\/]+)\/tasks/);
if (match && match[1]) {
task.setCategory(match[1]);
}
}
// 处理交互式题目标记
const interactiveSentences = ['This is an interactive task', 'This is a reactive problem'];
task.setInteractive(interactiveSentences.some(x => html.includes(x)));
// 提取时间和内存限制(增强版)
const limitNodes = elem.querySelector('#task-statement')?.previousElementSibling;
if (limitNodes) {
const timeLimitMatch = limitNodes.textContent.match(/([0-9.]+) ?sec/);
if (timeLimitMatch && timeLimitMatch[1]) {
task.setTimeLimit(parseFloat(timeLimitMatch[1]) * 1000);
}
const memoryLimitMatch = limitNodes.textContent.match(/(\d+) ?Mi?B/);
if (memoryLimitMatch && memoryLimitMatch[1]) {
task.setMemoryLimit(parseInt(memoryLimitMatch[1], 10));
}
} else {
console.warn('无法找到限制信息元素');
}
// 提取输入输出样例(增强版)
const inputs = [...elem.querySelectorAll('h3')]
.filter(el => el.textContent?.includes('入力例') || el.textContent?.includes('Sample Input'))
.map(el => {
const nextEl = el.nextElementSibling;
if (!nextEl) return null;
if (nextEl.tagName === 'PRE') return nextEl;
if (nextEl.tagName === 'DIV') return nextEl.nextElementSibling;
if (nextEl.children.length >= 3) return nextEl.children[2];
return nextEl.children[0];
})
.filter(el => el !== null);
const outputs = [...elem.querySelectorAll('h3')]
.filter(el => el.textContent?.includes('出力例') || el.textContent?.includes('Sample Output'))
.map(el => {
const nextEl = el.nextElementSibling;
if (!nextEl) return null;
if (nextEl.tagName === 'PRE') return nextEl;
if (nextEl.tagName === 'DIV') return nextEl.nextElementSibling;
if (nextEl.children.length >= 3) return nextEl.children[2];
return nextEl.children[0];
})
.filter(el => el !== null);
for (let i = 0; i < inputs.length && i < outputs.length; i++) {
task.addTest(inputs[i].textContent, outputs[i].textContent);
}
return task.build();
} catch (error) {
console.error(`解析AtCoder题目时出错: ${error.message}`, error);
throw new Error(`解析AtCoder题目失败: ${error.message}`);
}
}
src/parsers/problem/AtCoderProblemParser.ts(更新匹配模式)
public getMatchPatterns(): string[] {
return [
'https://atcoder.jp/contests/*/tasks/*',
'https://atcoder.jp/contests/*/tasks/*?lang=ja',
'https://atcoder.jp/contests/*/tasks/*?lang=en'
];
}
3. 重新构建扩展
npm install
npm run build
测试验证
测试用例设计
为确保修复有效,需要覆盖以下测试场景:
| 测试场景 | 测试步骤 | 预期结果 |
|---|---|---|
| 标准题目解析 | 访问https://atcoder.jp/contests/abc123/tasks/abc123_a | 成功解析题目信息,无错误 |
| 带语言参数的题目 | 访问https://atcoder.jp/contests/abc123/tasks/abc123_a?lang=en | 成功解析英文题目信息 |
| 交互式题目 | 访问包含交互式任务标记的题目 | 正确识别为交互式题目 |
| 特殊格式题目 | 访问包含复杂样例输入输出的题目 | 正确提取所有样例 |
| 竞赛任务列表 | 访问https://atcoder.jp/contests/abc123/tasks | 成功批量解析所有题目 |
测试结果验证
修复后,解析流程应能正确处理各种情况,不再抛出TypeError。解析器会:
- 对所有DOM查询结果进行空值检查
- 提供更灵活的URL匹配模式
- 在遇到错误时给出明确提示
- 实现备选方案处理部分元素缺失的情况
总结与展望
通过增强代码的健壮性和错误处理能力,我们成功解决了Competitive Companion解析AtCoder题目时的TypeError问题。这个修复不仅解决了当前的问题,也提高了代码对未来网站结构变化的适应能力。
未来可以从以下方面进一步优化:
- 实现更灵活的页面解析策略,减少对特定DOM结构的依赖
- 添加用户自定义解析规则的功能
- 建立解析器自动测试系统,及时发现网站结构变化
这些改进将使Competitive Companion在面对各类在线判题系统的变化时更加稳定可靠,为程序设计竞赛选手提供更好的支持。
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考



