新闻详情

新闻详情

首页 / 资讯中心 / 详情

Scrapy 爬虫实战:竞彩网 NBA 受注数据抓取与 TaoToken 配置

发布时间:2026/9/29 20:33:53来源:尧图网络
Scrapy 爬虫实战:竞彩网 NBA 受注数据抓取与 TaoToken 配置
1. 竞彩网 NBA 受注数据抓取难在哪竞彩网 NBA 受注数据是很多做体育数据看板、赔率监控、赛前分析的同学绕不开的一类数据源。它不像普通新闻页那样一页 HTML 就能扒完而是分成两层第一层是比赛列表接口返回当天或指定联赛的全部场次第二层是每场比赛的详情接口返回主客队场均得失分、近期胜负、交锋战绩、胜率这些受注参考指标。两层数据靠matchId串起来必须先在列表里拿到 id再带着 id 去请求详情最后把两份数据合并成一条完整记录。这个场景适合已经会写基础 Scrapy 爬虫、想练手「多级 Request items 字段拼装 json 输出」的同学。核心检索词就是 scrapy、爬虫、json、Request、items本文会围绕这几个点把竞彩网 NBA 受注数据的完整抓取流程走一遍包括 settings 配置、items 定义、两层请求的 callback 写法、json 落盘以及用 TaoToken 统一 Key 做接口调试的配置骨架。我试过直接拿浏览器里复制的 URL 去跑结果第二层详情请求全部 403后来才发现是请求头里少了 Referer 和 UA加上之后才稳定。下面按「先跑通列表、再拼详情、最后输出 json」的顺序来每一步都给可复制的代码。2. TaoToken 前置统一 Key 与接口调试骨架在写爬虫的过程中经常需要临时调一个模型接口来解析字段含义、生成 items 字段映射或者把抓到的 json 丢给模型做结构化校验。如果每个脚本都单独配一套 Key管理起来很乱。TaoToken 提供的是统一 Key 的方式一个 Key 走多个模型适合放在爬虫项目的配置里做辅助调试。官网地址是 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content API 入口是 https://taotoken.net/api 注意 API 地址不带 UTM 参数。你可以在控制台里创建 Key然后把它写进项目的settings.py或者单独的config.py爬虫主流程和调试脚本共用同一个 Key。具体操作路径先打开控制台 https://taotoken.net/console?utm_sourcetaotoken_aicg_blog_endutm_contentconsoleutm_campaignrewrite 在 API Keys 页面 https://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_contentapi-keysutm_campaignrewrite 新建一个 Key复制出来。如果你只是想验证某个模型能不能正确解析竞彩字段可以直接用模型对话页 https://taotoken.net/models?utm_sourcetaotoken_aicg_blog_endutm_contentmodelsutm_campaignrewrite 试一下把一段 json 贴进去问「这几个字段分别代表什么」。长期做编码和 Agent 任务的话Coding Plan 页面 https://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_contentcoding-planutm_campaignrewrite 有对应的套餐说明。注意TaoToken 在这里的角色是「接口调试与字段解析的辅助工具」不是爬虫的代理层也不要用它去绕过目标站点的访问限制。爬虫本身的请求头、频率控制还是要自己做好。配置骨架可以这样写放在项目根目录的config.py# config.py TAOTOKEN_API_BASE https://taotoken.net/api TAOTOKEN_API_KEY 你的Key # 数据库配置 DB_CONN { host: 127.0.0.1, port: 3306, user: dev, password: 123456, database: crawler, charset: utf8mb4, }然后在settings.py里引入爬虫主流程和调试脚本都从这里读避免 Key 散落在多个文件里。3. 可复制配置items 定义与 settings.json 输出先建 Scrapy 项目假设项目名叫lottery_crawlsscrapy startproject lottery_crawls cd lottery_crawls scrapy genspider bask_station webapi.sporttery.cn3.1 items.py 字段定义竞彩 NBA 受注数据字段比较多列表接口给一部分详情接口给另一部分items 要能装下两层合并后的结果# lottery_crawls/items.py import scrapy class BaskStation(scrapy.Item): no_str scrapy.Field() # 场次编号 league scrapy.Field() # 联赛简称 datetime scrapy.Field() # 比赛时间 home scrapy.Field() # 主队简称 away scrapy.Field() # 客队简称 a_sf scrapy.Field() # 客队近期胜负 h_sf scrapy.Field() # 主队近期胜负 battle scrapy.Field() # 交锋战绩 a_rate scrapy.Field() # 客队胜率 h_rate scrapy.Field() # 主队胜率 a_ds scrapy.Field() # 客队场均得失分 h_ds scrapy.Field() # 主队场均得失分3.2 settings.py 关键配置settings.py里要改的地方不多但每一项都影响能不能跑通。重点是ROBOTSTXT_OBEY关掉、DEFAULT_REQUEST_HEADERS补上、ITEM_PIPELINES打开、FEEDS配 json 输出# lottery_crawls/settings.py BOT_NAME lottery_crawls SPIDER_MODULES [lottery_crawls.spiders] NEWSPIDER_MODULE lottery_crawls.spiders ROBOTSTXT_OBEY False DOWNLOAD_DELAY 1 CONCURRENT_REQUESTS 4 DEFAULT_REQUEST_HEADERS { Accept: application/json, text/plain, */*, Accept-Language: zh-CN,zh;q0.9, Referer: https://www.sporttery.cn/, User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36, } ITEM_PIPELINES { lottery_crawls.pipelines.BaskStationPipeline: 300, } # json 输出按时间戳命名避免覆盖 from datetime import datetime FEEDS { foutput/bask_{datetime.now().strftime(%Y%m%d_%H%M%S)}.json: { format: json, encoding: utf8, indent: 2, ensure_ascii: False, }, } REQUEST_FINGERPRINTER_IMPLEMENTATION 2.7 TWISTED_REACTOR twisted.internet.asyncioreactor.AsyncioSelectorReactorFEEDS里用ensure_ascii: False是为了让中文正常显示不然 json 里全是\uXXXX校验的时候很难看。3.3 两层 Request 的 spider 写法列表接口返回的是按日期分组的matchInfoList每组下面有subMatchList每个子项就是一场比赛。拿到matchId后再发详情请求用meta把列表阶段拼好的 item 带过去# lottery_crawls/spiders/bask_station.py import json import scrapy from scrapy import Request from scrapy.http import TextResponse from lottery_crawls.items import BaskStation class BaskStationSpider(scrapy.Spider): name bask_station allowed_domains [webapi.sporttery.cn] def start_requests(self): url (https://webapi.sporttery.cn/gateway/jc/basketball/ getMatchListV1.qry?clientCode3001leagueId1) yield Request(urlurl, callbackself.parse, dont_filterTrue) def parse(self, response: TextResponse, **kwargs): data json.loads(response.text) date_group data[value][matchInfoList] for group in date_group: for item in group[subMatchList]: basketball_item BaskStation() basketball_item[no_str] item[matchNumStr] basketball_item[datetime] ( str(item[matchDate])[5:16] item[matchTime] ) basketball_item[home] item[homeTeamAbbName] basketball_item[away] item[awayTeamAbbName] basketball_item[league] item[leagueAbbName] match_id item[matchId] detail_url ( https://webapi.sporttery.cn/gateway/uniform/basketball/ fgetMatchFeatureV1.qry?termLimits10gmMatchId{match_id} ) yield Request( urldetail_url, meta{key: basketball_item}, callbackself.data_analysis, dont_filterTrue, ) def data_analysis(self, response: TextResponse): item response.meta[key] value json.loads(response.text)[value] a_d value[scoreAvg][awayGoalAvgCnt] a_s value[lossScoreAvg][awayLossGoalAvgCnt] item[a_ds] f{a_d}/{a_s} h_d value[scoreAvg][homeGoalAvgCnt] h_s value[lossScoreAvg][homeLossGoalAvgCnt] item[h_ds] f{h_d}/{h_s} a_win str(value[eachHomeAway][awayWinGoalMatchCnt]) a_lose str(value[eachHomeAway][awayLossGoalMatchCnt]) item[a_sf] f{a_win}胜/{a_lose}负 h_win str(value[eachHomeAway][homeWinGoalMatchCnt]) h_lose str(value[eachHomeAway][homeLossGoalMatchCnt]) item[h_sf] f{h_win}胜/{h_lose}负 item[a_rate] value[eachHomeAway][awayScoreRatio] % item[h_rate] value[eachHomeAway][homeScoreRatio] % home_name value[homeTeamShortName] b_win str(value[last][homeWinGoalMatchCnt]) b_lose str(value[last][homeLossGoalMatchCnt]) item[battle] f主队{home_name}{b_win}胜{b_lose}负 yield item这里有个容易踩的点callback只能写函数名字符串或者函数对象不能写成callbackself.data_analysis()带括号带括号就变成调用结果了Scrapy 会报TypeError。3.4 pipeline 落库与 json 双写如果只是要 json 输出FEEDS已经够了pipeline 可以只做数据清洗。但实际项目里通常还要落库这里给一个 pymysql 的管道和 json 输出并存# lottery_crawls/pipelines.py import logging import pymysql from config import DB_CONN class BaskStationPipeline: def __init__(self): self.table_name game_baskstation self.conn pymysql.connect( hostDB_CONN[host], portDB_CONN[port], userDB_CONN[user], passwordDB_CONN[password], databaseDB_CONN[database], charsetDB_CONN[charset], ) self.cursor self.conn.cursor() def close_spider(self, spider): self.cursor.close() self.conn.close() def process_item(self, item, spider): sql ( fINSERT INTO {self.table_name} (no_str, league, datetime_, home, away, a_sf, h_sf, battle, a_rate, h_rate, a_ds, h_ds) VALUES (%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s) ) try: self.cursor.execute(sql, ( item[no_str], item[league], item[datetime], item[home], item[away], item[a_sf], item[h_sf], item[battle], item[a_rate], item[h_rate], item[a_ds], item[h_ds], )) self.conn.commit() except Exception as e: logging.error(finsert failed: {e}) self.conn.rollback() return item建表语句参考CREATE TABLE game_baskstation ( id INT NOT NULL AUTO_INCREMENT, no_str VARCHAR(32) DEFAULT NULL, league VARCHAR(64) DEFAULT NULL, datetime_ VARCHAR(32) DEFAULT NULL, home VARCHAR(64) DEFAULT NULL, away VARCHAR(64) DEFAULT NULL, a_sf VARCHAR(32) DEFAULT NULL, h_sf VARCHAR(32) DEFAULT NULL, battle VARCHAR(128) DEFAULT NULL, a_rate VARCHAR(16) DEFAULT NULL, h_rate VARCHAR(16) DEFAULT NULL, a_ds VARCHAR(32) DEFAULT NULL, h_ds VARCHAR(32) DEFAULT NULL, PRIMARY KEY (id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;4. 验证请求跑起来看 json 结果配置写完先别急着落库用-o参数直接输出 json 验证字段是否拼对scrapy crawl bask_station -o output/test.json -s LOG_LEVELINFO跑完之后打开output/test.json正常应该看到类似这样的结构[ { no_str: 周一301, league: NBA, datetime: 01-15 09:30, home: 湖人, away: 凯尔特人, a_sf: 6胜/4负, h_sf: 7胜/3负, battle: 主队湖人5胜5负, a_rate: 60%, h_rate: 70%, a_ds: 112/108, h_ds: 115/105 } ]校验动作分三步第一看条数列表接口返回多少场json 里就应该有多少条少了说明详情请求有失败第二看字段有没有None或空字符串尤其是a_ds、h_ds这种拼接字段如果详情接口返回结构变了这里会直接报 KeyError第三看中文是否正常如果全是\u开头检查FEEDS里的ensure_ascii是不是漏了。如果想把 json 结果丢给模型做字段语义校验可以用 TaoToken 的模型对话页 https://taotoken.net/models?utm_sourcetaotoken_aicg_blog_endutm_contentmodelsutm_campaignrewrite 把一段 json 贴进去问「a_ds 和 h_ds 分别代表什么」比翻文档快。接入文档在 https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_contentdocutm_campaignrewrite 里面有 API 调用的完整参数说明。5. 本篇常见错排查5.1 403 Forbidden 或返回空 json最常见的原因是请求头不全。竞彩网的接口对Referer和User-Agent比较敏感settings.py里DEFAULT_REQUEST_HEADERS必须补上这两个。如果还是 403检查ROBOTSTXT_OBEY是不是True改成False。5.2 callback 报 TypeErrorTypeError: NoneType object is not callable或者TypeError: data_analysis() missing 1 required positional argument基本都是callback写成了callbackself.data_analysis()把括号去掉就行。callback传的是函数对象不是调用结果。5.3 allowed_domains 拦截详情请求列表接口和详情接口虽然都在webapi.sporttery.cn下但如果你后面换了别的域名比如webapi.sporttery.cn之外的接口allowed_domains里没加Scrapy 会直接过滤掉请求日志里显示Filtered offsite request。解决办法是把新域名加进allowed_domains或者临时用dont_filterTrue。5.4 json 输出中文乱码FEEDS里没写ensure_ascii: False或者写成了ensure_ascii: falsePython 里布尔值首字母大写。正确写法是ensure_ascii: False。5.5 详情接口 KeyError详情接口返回的 json 结构如果变了比如scoreAvg下面字段名改了value[scoreAvg][awayGoalAvgCnt]就会抛 KeyError。排查方法是先把response.text打印出来看实际结构再对照代码改字段名。建议在data_analysis里加一层try/except把失败的matchId记下来方便回头补抓。def data_analysis(self, response: TextResponse): item response.meta[key] try: value json.loads(response.text)[value] # ... 字段拼装 yield item except KeyError as e: self.logger.error(ffield missing: {e}, url{response.url})5.6 数据库连接报字符集错误pymysql 连接时charset不能写成utf-8要写成utf8mb4。端口号必须是整型写成字符串会报TypeError。6. 长期跑数据Key 和配置怎么管如果你只是偶尔跑一次竞彩 NBA 受注数据上面这套配置够用了。但如果要做成每天定时抓、多联赛并行、抓完还要做字段校验和异常告警那 Key 和配置的管理就要提前规划。我的做法是把 TaoToken 的 Key、数据库连接、目标联赛 id 全部收进config.py爬虫代码里只引用变量不写死任何敏感信息。这样换环境的时候只改一个文件。长期做编码和 Agent 任务的话Coding Plan 页面 https://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_contentcoding-planutm_campaignrewrite 有对应的方案说明适合把模型调用嵌进日常开发流程。ClaudeCode 相关的接入说明在 https://taotoken.net/claudecode-anthropic?utm_sourcetaotoken_aicg_blog_endutm_contentclaudecode-anthropicutm_campaignrewrite 如果你用 ClaudeCode 做爬虫脚本的辅助编写可以参考。最后提醒一句抓数据要控制频率DOWNLOAD_DELAY别设太小CONCURRENT_REQUESTS也别开太高竞彩网这类接口对高频请求是有风控的。跑之前先小批量验证确认字段和条数都对再放开跑全量。
网站建设高端定制企业官网
RELATED

相关资讯

更多精彩内容,欢迎继续阅读

较早相关资讯

最新相关资讯

OpenAI把Codex变成全民工具后,我用TaoToken统一Key接上了Cline和CC Switch 2026/9/29 22:39:55

OpenAI把Codex变成全民工具后,我用TaoToken统一Key接上了Cline和CC Switch

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

阅读更多 →
基于单目视频三维实时重构的应急抢险“侦察-建模-研判-调度”全链路智能化 2026/9/29 22:39:55

基于单目视频三维实时重构的应急抢险“侦察-建模-研判-调度”全链路智能化

摘要突发灾害与安全生产事故应急抢险场景具有现场环境突变、遮挡严重、风险耦合、态势瞬息万变的特征,传统应急作业普遍存在二维侦察信息片面、三维场景缺失、人工建模耗时滞后、风险研判主观、指挥调度粗放等痛点,难以满足黄金救援时段的精准处置需求。…

阅读更多 →
HelloAgents ToolResponse 工具响应协议:为什么字符串返回值正在拖累你的智能体 2026/9/29 22:39:54

HelloAgents ToolResponse 工具响应协议:为什么字符串返回值正在拖累你的智能体

HelloAgents ToolResponse 工具响应协议:为什么字符串返回值正在拖累你的智能体 【免费下载链接】HelloAgents A agent framework based on the tutorial hello-agents 项目地址: https://gitcode.com/gh_mirrors/he/HelloAgents 在构建 AI 智能体&#xff0…

阅读更多 →
Sealos云原生平台:运维团队从12人减至3人的实战复盘 2026/9/29 22:39:54

Sealos云原生平台:运维团队从12人减至3人的实战复盘

那条标题挂出来之后,我被同行问得最多的一句是:Sealos到底是什么东西,能把运维团队从12个人缩到3个人?我理解大家为什么这么惊讶。12到3,这个数字听起来像在讲故事,甚至像是在吓唬人。但作为那个被HR半开玩…

阅读更多 →
用BaseAdapter时,ListView的onItemClick狂报ClassCastException:java.lang.Boolean——从Adapter.getItemViewType到T 2026/9/29 22:39:53

用BaseAdapter时,ListView的onItemClick狂报ClassCastException:java.lang.Boolean——从Adapter.getItemViewType到T

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

阅读更多 →
2026计算机毕业设计指南:把开源项目消化成自己的高分作品 2026/9/29 22:39:45

2026计算机毕业设计指南:把开源项目消化成自己的高分作品

每年到了三四月份,我的各种社交平台私信里就会被一类问题塞满:“有没有现成的计算机毕业设计开源代码?”“求一个带数据库、能演示的Java毕设”“2026年毕业,现在开始准备还来得及吗?”问的人一多,我反而特…

阅读更多 →

今日资讯

本周资讯

本月资讯

看完文章仍有疑问?

联系尧图顾问,获取一对一建站咨询

立即免费咨询 📞 400-888-8888
📞 ✉