文章目录
- 前言
- 重构采集流程
- 完整工程代码
- 更多采集场景
- SERP API
- 网页采集 API
- 通用采集 API
- 数据集市场
- Dashboard 控制台
- 总结
前言
大家好,我是颜颜yan_。
最近在做视频数据采集项目时,遇到了各种实际问题。页面大量使用动态加载,数据并不在初始 HTML 中,需要分析页面结构或调用内部接口。更麻烦的是页面结构经常变化,之前写好的解析规则过一段时间就会失效。
批量采集时网络异常问题更加明显:IP 限制、频率控制、浏览器环境检测接连出现。为了应对这些,又得维护代理池、请求重试、Cookie 管理等一堆基础设施。
重构采集流程
重新调整方案后,稳定性和效率都提升了不少。
现在我的整个流程变成:搜索关键词→ 获取视频链接 → 提交采集任务 → 获取结构化数据 → 进入分析流程。
我把最重要的采集部分交给了 Dataify,只在工程里保留"搜索入口 + 链接提取"这部分逻辑。
我在Dataify中把对应链接设置好,设置好参数。右侧就会出现对应的代码,直接把这个模板代码接入到我的工程代码里面。
importrequests url="https://scraperapi.dataify.com/builder"headers={"Authorization":"你的API key","Content-Type":"application/x-www-form-urlencoded",}data={"spider_name":"youtube.com","spider_id":"youtube_video_by-url","spider_parameters":'[{"url":"https://www.youtube.com/watch?v=_SdpvpvVrLY"}]',"spider_universal":'{"resolution":"<=360p","video_codec":"vp9","audio_format":"opus","bitrate":"<=320","subtitles_language":"ab","selected_only":"false"}',"spider_errors":"true","file_name":"{{TasksID}}",}response=requests.post(url,headers=headers,data=data)print(response.text)我运行后在Dataify任务列表就可以看到对应任务。
完成任务后,在 Dashboard 点击下载,可导出 JSON、CSV、XLSX 三种格式的结果。从请求到最终结果只需要几秒钟,方便多了。
返回的元数据是已经清洗好的结构化字段,包括标题、视频 ID、缩略图、播放量、发布时间、点赞数、频道信息、时长、描述、字幕状态、分辨率等。
完整工程代码
importargparseimportjsonimportosimportrefromhtmlimportunescapefromurllib.parseimporturlencodefromurllib.requestimportRequest,urlopen SEARCH_URL="https://www.youtube.com/results?search_query=%E9%AA%91%E8%A1%8C%E7%AC%AC%E4%B8%80%E8%A7%86%E8%A7%92"DATAIFY_URL="https://scraperapi.dataify.com/builder"DATAIFY_TOKEN=os.getenv("DATAIFY_TOKEN","APIkey")DEFAULT_SCROLL_PAGES=10VIDEO_ID_PATTERN=re.compile(r"/watch\?v=([a-zA-Z0-9_-]{11})")API_KEY_PATTERN=re.compile(r'"INNERTUBE_API_KEY"\s*:\s*"([^"]+)"')CLIENT_NAME_PATTERN=re.compile(r'"INNERTUBE_CLIENT_NAME"\s*:\s*"([^"]+)"')CLIENT_VERSION_PATTERN=re.compile(r'"INNERTUBE_CLIENT_VERSION"\s*:\s*"([^"]+)"')defmake_watch_url(video_id):returnf"https://www.youtube.com/watch?v={video_id}"defunique_links(video_links):links=[]seen=set()forlinkinvideo_links:iflinkinseen:continueseen.add(link)links.append(link)returnlinksdefextract_video_links(html):"""Return unique YouTube watch URLs in first-seen order."""html=unescape(html.replace("\\u0026","&"))returnunique_links(make_watch_url(video_id)forvideo_idinVIDEO_ID_PATTERN.findall(html))defextract_json_object(text,start_index):depth=0in_string=Falseescaped=Falseforindexinrange(start_index,len(text)):char=text[index]ifin_string:ifescaped:escaped=Falseelifchar=="\\":escaped=Trueelifchar=='"':in_string=Falsecontinueifchar=='"':in_string=Trueelifchar=="{":depth+=1elifchar=="}":depth-=1ifdepth==0:returntext[start_index:index+1]raiseValueError("Could not find the end of the JSON object.")defextract_yt_initial_data(html):marker="ytInitialData"marker_index=html.find(marker)ifmarker_index==-1:returnNonestart_index=html.find("{",marker_index)ifstart_index==-1:returnNonereturnjson.loads(extract_json_object(html,start_index))defwalk_json(value):ifisinstance(value,dict):yieldvalueforchildinvalue.values():yieldfromwalk_json(child)elifisinstance(value,list):forchildinvalue:yieldfromwalk_json(child)defextract_video_links_from_data(data):video_ids=[]foriteminwalk_json(data):video=item.get("videoRenderer")ifnotisinstance(video,dict):continuevideo_id=video.get("videoId")ifvideo_id:video_ids.append(video_id)returnunique_links(make_watch_url(video_id)forvideo_idinvideo_ids)defextract_continuation_token(data):foriteminwalk_json(data):command=item.get("continuationCommand")ifisinstance(command,dict)andcommand.get("token"):returncommand["token"]continuation=item.get("nextContinuationData")ifisinstance(continuation,dict)andcontinuation.get("continuation"):returncontinuation["continuation"]returnNonedefextract_innertube_config(html):api_key=API_KEY_PATTERN.search(html)client_name=CLIENT_NAME_PATTERN.search(html)client_version=CLIENT_VERSION_PATTERN.search(html)return{"api_key":api_key.group(1)ifapi_keyelseNone,"client_context":{"client":{"clientName":client_name.group(1)ifclient_nameelse"WEB","clientVersion":client_version.group(1)ifclient_versionelse"2.20240701.00.00",}},}deffetch_search_html(search_url):headers={"Accept-Language":"zh-CN,zh;q=0.9,en;q=0.8","User-Agent":("Mozilla/5.0 (Windows NT 10.0; Win64; x64) ""AppleWebKit/537.36 (KHTML, like Gecko) ""Chrome/126.0.0.0 Safari/537.36"),}request=Request(search_url,headers=headers)withurlopen(request,timeout=30)asresponse:charset=response.headers.get_content_charset()or"utf-8"returnresponse.read().decode(charset,errors="replace")deffetch_continuation_data(token,client_context,api_key):ifnotapi_key:raiseValueError("Could not find YouTube INNERTUBE_API_KEY in the search page.")body=json.dumps({"context":client_context,"continuation":token,}).encode("utf-8")request=Request(f"https://www.youtube.com/youtubei/v1/search?key={api_key}",data=body,headers={"Content-Type":"application/json","User-Agent":("Mozilla/5.0 (Windows NT 10.0; Win64; x64) ""AppleWebKit/537.36 (KHTML, like Gecko) ""Chrome/126.0.0.0 Safari/537.36"),},method="POST",)withurlopen(request,timeout=30)asresponse:charset=response.headers.get_content_charset()or"utf-8"returnjson.loads(response.read().decode(charset,errors="replace"))defget_search_video_links(search_url,limit=None,scroll_pages=10,fetch_html=fetch_search_html,fetch_continuation=fetch_continuation_data,):html=fetch_html(search_url)initial_data=extract_yt_initial_data(html)config=extract_innertube_config(html)ifinitial_data:links=extract_video_links_from_data(initial_data)continuation=extract_continuation_token(initial_data)else:links=extract_video_links(html)continuation=Nonefor_inrange(1,max(scroll_pages,1)):ifnotcontinuation:breakpage_data=fetch_continuation(continuation,config["client_context"],config["api_key"])links=unique_links(links+extract_video_links_from_data(page_data))continuation=extract_continuation_token(page_data)iflimitandlen(links)>=limit:breakiflimit:returnlinks[:limit]returnlinksdefbuild_spider_parameters(video_links):returnjson.dumps([{"url":url}forurlinvideo_links],ensure_ascii=False)defsubmit_to_dataify(video_links):ifnotvideo_links:raiseValueError("No YouTube video links found.")headers={"Authorization":f"Bearer{DATAIFY_TOKEN}","Content-Type":"application/x-www-form-urlencoded",}data={"spider_name":"youtube.com","spider_id":"youtube_video-post_by-url","spider_parameters":build_spider_parameters(video_links),"spider_errors":"true","file_name":"{{TasksID}}",}encoded_data=urlencode(data).encode("utf-8")request=Request(DATAIFY_URL,data=encoded_data,headers=headers,method="POST")withurlopen(request,timeout=60)asresponse:charset=response.headers.get_content_charset()or"utf-8"returnresponse.read().decode(charset,errors="replace")defprompt_scroll_pages(default=DEFAULT_SCROLL_PAGES):whileTrue:value=input(f"请输入滚动页数,直接回车默认{default}页: ").strip()ifnotvalue:returndefaulttry:pages=int(value)exceptValueError:print("请输入大于 0 的整数。")continueifpages>0:returnpagesprint("请输入大于 0 的整数。")defparse_args():parser=argparse.ArgumentParser(description="Extract YouTube search-result video links and submit them to Dataify.")parser.add_argument("--search-url",default=SEARCH_URL,help="YouTube search result URL.")parser.add_argument("--limit",type=int,help="Only submit the first N video links.")parser.add_argument("--scroll-pages",type=int,help="How many search-result pages to read, including the initial page.",)parser.add_argument("--dry-run",action="store_true",help="Print links without submitting.")returnparser.parse_args()defmain():args=parse_args()scroll_pages=args.scroll_pagesorprompt_scroll_pages()video_links=get_search_video_links(args.search_url,args.limit,scroll_pages)print(f"Found{len(video_links)}video links:")forlinkinvideo_links:print(link)ifargs.dry_run:returnprint(submit_to_dataify(video_links))if__name__=="__main__":main()更多采集场景
SERP API
除了视频数据,Dataify 还覆盖了其他几类常见场景:
SERP API用于实时获取搜索引擎结果(Google、Bing、DuckDuckGo 等),包括自然搜索结果、广告位、People Also Ask 等 SERP Feature,适合 SEO 监控、关键词排名和竞品分析。
网页采集 API
网页采集 API适合商品详情页、新闻文章、企业黄页等内容,提交 URL 后可以处理 JS 渲染、反爬和数据返回,输出格式支持 HTML、Markdown 或结构化 JSON。
通用采集 API
通用采集 API是长尾站点的兜底方案,适合没有专门模板的站点。对于无法套用预置模板的网站,可以通过通用接口发起请求,由平台处理访问路由、浏览器指纹、JS 渲染和验证码等复杂流程。
数据集市场
Dataify 还提供数据集市场,公开了 1000+ 数据集,覆盖电商、社交媒体、音视频、AI 训练、金融、医疗、法律等多个方向。如果不想自己采集,可以直接使用现成的数据集进行分析或训练。
| 数据集类型 | 使用场景 |
|---|---|
| 电商数据集 | 商品监控、评论分析、选品研究 |
| 社交媒体数据集 | 达人分析、内容趋势、舆情监测 |
| 视频数据集 | 视频推荐、内容理解、账号分析 |
| 行业数据集 | AI 训练、行业研究、知识库构建 |
Dashboard 控制台
Dashboard 控制台集成了 API 管理(创建、轮换 API Key,查看调用配额)、任务管理(提交、查询和管理采集任务)、数据集市场、代理资源监控和账单日志。
整体来看,它像是采集任务的驾驶舱,能查看任务状态、响应时间、成功率、积分消耗和历史日志,方便后续排查问题和评估采集稳定性。
总结
在视频数据采集项目中,真正耗时是维护那些基础设施,动态加载处理、页面结构适配、反爬应对、代理管理这些底层工作占据了大量精力。
把这部分交给 Dataify 之后,整个流程变得很直接,搜索关键词、提取链接、提交任务、拿到结构化数据,然后进入业务分析,开发重心终于回到核心逻辑上。
如果你也在做类似的数据采集项目,不妨试试 Dataify。
立即体验:https://dataify.com?utm_source=yyyan&utm_term=01
我是颜颜yan_,觉得好的话点个赞吧!