如何从101围棋网提取围棋题并转换为SGF格式?
从101围棋网站抓取题目并转换为SGF格式的方案
核心思路:直接解析qqdata变量(最优方案)
网站页面末尾<script>标签里的qqdata变量就是题目数据的源头,完全不需要从canvas做视觉提取,你提到的第三方Python项目也是基于这个变量解析,并非视觉识别。
步骤1:提取页面中的qqdata
- Python环境:用
requests拉取页面源码,再通过正则表达式匹配var qqdata = (.+?);提取JSON格式的题目数据; - TypeScript(Node.js环境):用
axios获取页面内容,配合正则或cheerio定位目标script标签后提取变量内容。
步骤2:自定义坐标转SGF坐标
101围棋的自定义坐标以棋盘左上角为原点,SGF坐标规则为列用a-o(15路)/a-s(19路),行用1-15/19,且先列后行,转换逻辑:
- 101的x值对应SGF的列:将数字转为小写字母(x=0→a,x=1→b,以此类推);
- 101的y值对应SGF的行:因101的y=0是棋盘最上方,SGF的行1是最下方,需反转计算(如19路棋盘,SGF行号=19 - y)。
步骤3:生成标准SGF格式
SGF基础结构为(;GM[1]FF[4]SZ[19];B[aa];W[ab]...),其中:
GM[1]标记为围棋,FF[4]指定SGF版本,SZ[19]对应棋盘大小(根据题目实际调整);- 遍历
qqdata中的黑白棋子位置,分别用B[坐标]和W[坐标]追加到SGF结构中,空棋盘仅保留头部即可。
具体实现示例
Python版本
import requests import re import json def fetch_qqdata(url: str): response = requests.get(url) match = re.search(r'var qqdata = (.+?);', response.text, re.DOTALL) if not match: raise ValueError("未找到qqdata变量") return json.loads(match.group(1)) def convert_to_sgf(qqdata): board_size = qqdata.get('size', 19) sgf_parts = [f'(;GM[1]FF[4]SZ[{board_size}]'] # 处理黑棋 for stone in qqdata.get('black', []): x, y = stone['x'], stone['y'] col = chr(ord('a') + x) row = str(board_size - y) sgf_parts.append(f';B[{col}{row}]') # 处理白棋 for stone in qqdata.get('white', []): x, y = stone['x'], stone['y'] col = chr(ord('a') + x) row = str(board_size - y) sgf_parts.append(f';W[{col}{row}]') sgf_parts.append(')') return ''.join(sgf_parts) # 使用示例 if __name__ == '__main__': target_url = "目标题目链接" qqdata = fetch_qqdata(target_url) sgf_content = convert_to_sgf(qqdata) print(sgf_content)
TypeScript(Node.js)版本
import axios from 'axios'; async function fetchQQData(url: string) { const response = await axios.get(url); const match = response.data.match(/var qqdata = (.+?);/s); if (!match) { throw new Error('未找到qqdata变量'); } return JSON.parse(match[1]); } function convertToSGF(qqdata: any): string { const boardSize = qqdata.size || 19; const sgfParts: string[] = [`(;GM[1]FF[4]SZ[${boardSize}]`]; // 处理黑棋 qqdata.black?.forEach((stone: {x: number, y: number}) => { const col = String.fromCharCode('a'.charCodeAt(0) + stone.x); const row = boardSize - stone.y; sgfParts.push(`;B[${col}${row}]`); }); // 处理白棋 qqdata.white?.forEach((stone: {x: number, y: number}) => { const col = String.fromCharCode('a'.charCodeAt(0) + stone.x); const row = boardSize - stone.y; sgfParts.push(`;W[${col}${row}]`); }); sgfParts.push(')'); return sgfParts.join(''); } // 使用示例 (async () => { const targetUrl = "目标题目链接"; const qqdata = await fetchQQData(targetUrl); const sgfContent = convertToSGF(qqdata); console.log(sgfContent); })();
注意事项
- 部分页面需登录访问,此时需在请求中携带登录后的Cookie(Python用
requests.Session(),TS用axios的headers配置); - 若
qqdata结构发生变动,需根据实际返回的JSON调整解析逻辑; - 遵守网站规则,避免高频请求导致IP被封禁。
内容的提问来源于stack exchange,提问作者psygo
相关产品推荐
相关产品推荐

