简介:这份资源是一套面向互联网营销与数据分析人员的抖音关键词搜索自动化脚本,适合具备一定Python基础、希望批量采集抖音视频与用户信息的开发者使用。包内共5个文件,包含Python脚本、批处理文件、APK安装包及依赖清单等,压缩包约9.8MB,覆盖从环境配置到数据抓取的主要环节。脚本涉及关键词匹配、网络爬虫、数据解析与API调用等核心知识点,并借助中间人代理方式拦截网络通信,便于调试与数据获取,同时提供依赖文件方便快速搭建运行环境。已有922人学习下载,读者可据此了解抖音搜索脚本的整体实现思路,掌握请求构造、反爬应对、HTML与JSON解析等实用技巧,并参考代理配置与依赖管理方法完成本地部署,为后续二次开发与数据分析打下基础。
1. 抖音关键词搜索脚本到底在搜什么:从手动刷到批量拉取的边界
刷抖音找素材的人都有过这种体验:想找某个话题下的爆款视频,手动搜关键词、往下滑、记标题、点进主页、复制链接,一条两条还行,一百条就是纯体力活。抖音关键词搜索脚本要解决的就是这件事——把「搜索关键词 → 翻页 → 提取视频信息」这条链路自动化,让程序替你把结果拉回来。它适合做选题调研、竞品监控、素材归集的运营和开发,不适合想刷量、刷互动的人,那是另一条路,风险也完全不同。
需要先把预期摆正:抖音没有开放公开的搜索 API 给普通开发者,所以脚本本质上是在模拟一个真实浏览器或客户端的请求行为。这意味着两件事——第一,你能拿到的是页面渲染出来的公开信息,比如标题、作者、点赞数、视频链接;第二,请求频率、登录态、签名参数任何一个不对,返回的就是空列表或者验证页。这篇笔记按「先跑通最小链路,再谈稳定和规模」的顺序讲,重点放在怎么用、参数怎么调、哪里会翻车。
2. 先想清楚抓什么:搜索页的数据结构和字段取舍
2.1 搜索结果页里真正能拿到的字段
打开抖音网页版搜索一个关键词,你会看到一列视频卡片。每张卡片在 DOM 里对应的信息大致是这几类:视频标题或描述文案、作者昵称、作者主页链接、视频详情页链接、点赞数(有时还有评论数和收藏数)、封面图地址。这些是「列表页可见」的字段,不需要点进详情就能拿到。
再往里一层,点进视频详情页能拿到更完整的描述、话题标签、音乐信息、发布时间。但每多进一层,请求数就翻一倍,被限流的概率也成倍上升。我的做法是:列表页能拿到的字段全部拿全,详情页只在确实需要发布时间或完整话题时才补抓,而且做去重和缓存,同一个视频不重复请求。
字段取舍上有个容易忽略的点——点赞数在列表页经常是「1.2万」这种带单位的字符串,直接存下来没法做数值排序。要么在脚本里写个转换函数把它还原成整数,要么就接受它是字符串,后续用的时候再处理。我一般在前端就转掉,省得后面每处都判断。
2.2 用请求还是用浏览器:两种技术路线的选型
抓取抖音搜索页有两条主流路线,选错了后面全是坑。
第一条是纯 HTTP 请求路线,用 requests 或 httpx 直接构造搜索接口请求,解析返回的 JSON。优点是快、省资源、容易并发;缺点是抖音的接口带签名参数(常见的是 a_bogus 或 x-bogus 这类),签名算法会变,一旦变了你的请求全部返回空。这条路适合有逆向能力、能持续跟进签名变化的团队。
第二条是浏览器自动化路线,用 Playwright 或 Selenium 驱动真实浏览器,让页面自己完成签名和渲染,你只负责从 DOM 里读数据。优点是签名问题交给浏览器,稳定性高得多;缺点是慢、吃内存、并发上不去。对绝大多数做运营调研的人来说,这条路才是能长期跑下去的。
我一般会这么选:如果只是每天拉几百条做选题,直接上 Playwright;如果要做到几万条级别并且有专人维护,才考虑去啃接口签名。下面两章的代码都基于 Playwright,因为它是新手能最快跑通、熟手也愿意用来兜底的方案。
提示:无论走哪条路,都不要在未登录状态下高频请求。未登录态的搜索页返回内容更少,且更容易触发验证。
3. 用 Playwright 跑通关键词搜索的最小链路
3.1 环境准备和依赖安装
先把环境搭起来。Python 3.9 以上即可,Playwright 对版本不挑。装依赖就三行命令,注意 playwright 装完还要单独装一次浏览器内核,这一步经常被漏掉,漏了就会报「找不到浏览器可执行文件」。
pip install playwright pandas playwright install chromium第一行装 Playwright 本体和 pandas(后面存表格用)。第二行才是关键,它会把 Chromium 内核下载到本地,Playwright 默认用这个内核跑。如果你在服务器上跑,没有图形界面,Chromium 也能以 headless 模式运行,不需要额外装桌面环境。
参数上唯一要注意的是playwright install后面跟的内核名,写chromium就够了,不要写chrome,那是调用系统已装的 Chrome,服务器上通常没有。
3.2 打开搜索页并等待结果渲染
抖音搜索页是动态渲染的,page.goto返回时 DOM 里往往还没有视频卡片。必须显式等待卡片元素出现,否则你抓到的永远是空列表。下面是最小可运行版本。
from playwright.sync_api import sync_playwright import time def search_douyin(keyword, max_scroll=5): results = [] with sync_playwright() as p: # headless=False 方便第一次调试时看页面 browser = p.chromium.launch(headless=True) context = browser.new_context( 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" ) page = context.new_page() url = f"https://www.douyin.com/search/{keyword}" page.goto(url, wait_until="domcontentloaded") # 等待视频卡片出现,超时就放弃 try: page.wait_for_selector('div[data-e2e="scroll-list"]', timeout=15000) except Exception as e: print("列表未加载:", e) browser.close() return results for i in range(max_scroll): # 滚动到底触发懒加载 page.mouse.wheel(0, 3000) time.sleep(2) # 给渲染留时间,别省这一秒 # 抓取卡片文本 cards = page.query_selector_all('li[data-e2e="scroll-list-item"]') for c in cards: title_el = c.query_selector('div[data-e2e="video-desc"]') link_el = c.query_selector('a') results.append({ "title": title_el.inner_text() if title_el else "", "link": link_el.get_attribute("href") if link_el else "", }) browser.close() return results if __name__ == "__main__": data = search_douyin("露营装备", max_scroll=3) print(len(data), "条")逻辑上分四步:启动浏览器并设置 UA、打开搜索页、等待列表容器出现、滚动若干次后统一抓取。wait_for_selector里的选择器div[data-e2e="scroll-list"]是抖音列表的容器标识,>import pandas as pd def parse_like(s): # "1.2万" -> 12000, "345" -> 345 if not s: return 0 s = s.strip() if "万" in s: return int(float(s.replace("万", "")) * 10000) return int(s) if s.isdigit() else 0 def save_results(rows, path="douyin_search.csv"): df = pd.DataFrame(rows) if "like" in df.columns: df["like_num"] = df["like"].apply(parse_like) df = df.drop_duplicates(subset=["link"]) df.to_csv(path, index=False, encoding="utf-8-sig") print(f"写入 {len(df)} 条到 {path}") # 用法:把 3.2 抓到的 results 传进来 # save_results(data)
parse_like处理的是带单位的字符串,drop_duplicates按链接去重,因为滚动加载时同一条视频可能被重复渲染。encoding="utf-8-sig"是为了 Excel 打开不乱码,这个细节很多人踩过坑,用普通 utf-8 存中文 CSV,双击打开就是乱码。
注意:这套代码跑通不代表能无限跑。连续搜索多个关键词时,每个关键词之间至少间隔十几秒,否则很快会遇到验证页。
4. 让脚本能长期跑:登录态、翻页和频率控制
4.1 复用登录态,别每次都重新登
未登录状态下抖音搜索返回的结果有限,而且更容易触发验证。正确做法是第一次手动登录一次,把浏览器的登录态(cookies 和 localStorage)保存下来,后续脚本直接加载,不用重复登录。
def save_login_state(): with sync_playwright() as p: browser = p.chromium.launch(headless=False) context = browser.new_context() page = context.new_page() page.goto("https://www.douyin.com") input("手动登录完成后按回车继续...") # 把登录态存到本地文件 context.storage_state(path="state.json") browser.close() def search_with_state(keyword): with sync_playwright() as p: browser = p.chromium.launch(headless=True) # 加载之前保存的登录态 context = browser.new_context(storage_state="state.json") page = context.new_page() page.goto(f"https://www.douyin.com/search/{keyword}") # ...后续抓取逻辑同上storage_state是 Playwright 提供的登录态序列化机制,存下来的是一个 JSON,包含 cookies 和 localStorage。第一次跑save_login_state,手动扫码登录,回车后生成state.json;之后所有抓取都走search_with_state,加载这个文件即可。这个文件等于你的登录凭证,别提交到代码仓库,也别随便分享。
登录态不是永久的,一般几天到一两周会失效。失效的表现是搜索结果变少或者直接跳登录页,这时候重新跑一次save_login_state就行。可以在脚本里加个判断:如果抓到的条数为 0,就提示登录态可能过期。
4.2 翻页和滚动到底的处理
抖音搜索结果是无限滚动的,没有传统意义上的「下一页」按钮。翻页靠的是不断滚动触发懒加载,直到页面不再出现新卡片为止。判断「到底了」有个简单办法:记录上一次滚动后的卡片数量,如果连续两次滚动数量没变化,就认为加载完了。
def scroll_until_end(page, max_round=20): last_count = 0 for _ in range(max_round): page.mouse.wheel(0, 4000) page.wait_for_timeout(2000) cards = page.query_selector_all('li[data-e2e="scroll-list-item"]') if len(cards) == last_count: break # 数量不再增长,到底了 last_count = len(cards) return last_countmax_round是安全上限,防止页面异常时死循环。wait_for_timeout是 Playwright 自带的等待,比time.sleep更贴合浏览器事件循环。连续两轮数量不变就退出,这个判断在绝大多数情况下够用。
4.3 频率控制和并发边界
这是整个脚本能不能长期跑的核心。抖音对搜索行为的频率很敏感,短时间内大量请求必然触发验证。我的经验参数是:单个关键词的完整抓取控制在 30 秒以上,多个关键词之间随机间隔 15 到 40 秒,一天总量控制在几百条以内。
并发方面,浏览器自动化路线基本不要想高并发。开多个浏览器实例会迅速吃满内存,而且并发请求更容易被识别。如果确实需要更大规模,正确方向是分布式加代理池,但那是另一个量级的工程,不是这篇能覆盖的。对多数人来说,串行加合理间隔,一天稳定拉几百条,已经够做选题调研了。
提示:把间隔时间写成随机值而不是固定值,固定间隔的行为特征太明显。用
random.uniform(15, 40)这种。
5. 关键词搜索脚本的避坑清单:五个真实翻车现场
5.1 抓到空列表,但页面明明有内容
现象:脚本跑完返回 0 条,手动打开同一个 URL 却能看到一堆视频。
原因:九成是等待时机不对。page.goto默认在load事件后返回,但抖音的卡片是异步渲染的,此时 DOM 里还没有卡片。另一个可能是选择器写错了,抖音前端改版会换>import sqlite3 def init_db(path="douyin.db"): conn = sqlite3.connect(path) conn.execute("""CREATE TABLE IF NOT EXISTS videos ( link TEXT PRIMARY KEY, title TEXT, keyword TEXT, like_num INTEGER, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP )""") conn.commit() return conn def save_incremental(conn, rows, keyword): new_count = 0 for r in rows: try: conn.execute( "INSERT INTO videos (link, title, keyword, like_num) VALUES (?,?,?,?)", (r["link"], r["title"], keyword, r.get("like_num", 0)) ) new_count += 1 except sqlite3.IntegrityError: pass # 链接已存在,跳过 conn.commit() return new_count
link设成主键,重复插入会抛IntegrityError,捕获后跳过,天然实现去重。keyword字段记下这条视频是从哪个词搜出来的,方便后面分析不同关键词的内容重合度。这个表结构简单,但足够支撑几个月的增量抓取。
验证抓取质量有个笨办法但很有效:随机抽 10 条抓到的链接,手动打开,核对标题和点赞数是否一致。如果对不上,说明选择器抓错了元素,或者页面结构和你的假设有偏差。我一般在新写或改版后必做这一步,比看日志靠谱。
最后一个习惯:把每次抓取的元信息也记下来——关键词、抓取时间、抓到条数、新增条数。跑一段时间后回看这些数字,能提前发现登录态失效、限流加剧这类问题。比如某天新增条数突然掉到个位数,多半就是被限了,这时候停下来调整比继续硬跑明智。这套东西不复杂,难的是持续维护的耐心,抖音前端一改版,选择器就得跟着更新,这是做这类脚本绕不开的日常。希望帮到你。
本文还有配套的精品资源,点击获取