基于 Python 爬取 TikTok 搜索数据 Tiktok爬虫(2025.3.17)

news2025/3/31 6:14:22

1. 前言

在数据分析和网络爬虫的应用场景中,我们经常需要获取社交媒体平台的数据,例如 TikTok。本篇文章介绍如何使用 Python 爬取 TikTok 用户搜索数据,并解析其返回的数据。

结果截图

2. 项目环境准备

在正式运行代码之前,我们需要安装相关的 Python 库:

pip install requests pandas execjs loguru

此外,我们需要一个 JavaScript 运行环境(如 Node.js),用于执行加密签名代码。

3. 代码解析

3.1 初始化爬虫类

我们创建 TiktokUserSearch 类,并在初始化方法 __init__ 中设置请求头信息,并初始化输出文件。

class TiktokUserSearch:
    def __init__(self, output_file=None):
        self.headers = {  # 设置请求头
            "user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 ...",
            "referer": "https://www.tiktok.com/"
        }
        self.cookies = None
        self.output_file = output_file if output_file else f'tiktok_videos_{datetime.now().strftime("%Y%m%d_%H%M%S")}.csv'

3.2 处理 Cookie

我们需要将 TikTok 的 cookie 从字符串转换成字典格式,以便后续请求使用。

    def cookie_str_to_dict(self, cookie_str) -> dict:
        cookie_dict = {}
        cookies = [i.strip() for i in cookie_str.split('; ') if i.strip() != ""]
        for cookie in cookies:
            key, value = cookie.split('=', 1)
            cookie_dict[key] = value
        return cookie_dict

3.3 发送请求

TikTok 需要使用 X-Bogus 进行签名,我们需要执行 JavaScript 代码来获取该参数。

为了防止网络不稳定,设置三次重试机制。

可根据自己需求设置代理。

    def get(self, keyword, cursor, search_id, cookie_str):
        self.cookies = self.cookie_str_to_dict(cookie_str)
        url = "https://www.tiktok.com/api/search/general/full/"
        if cursor == "0":
            focus_state = "true"
        else:
            focus_state = "false"
        params = {
            "WebIdLastTime": f"{int(time.time())}",
            "aid": "1988",
            "app_language": "zh-Hans",
            "app_name": "tiktok_web",
            "browser_language": "zh-CN",
            "browser_name": "Mozilla",
            "browser_online": "true",
            "browser_platform": "Win32",
            "browser_version": self.headers['user-agent'].replace('Mozilla/', ''),
            "channel": "tiktok_web",
            "cookie_enabled": "true",
            "cursor": cursor,
            "device_id": "7339506347602019870",
            "device_platform": "web_pc",
            "focus_state": focus_state,
            "from_page": "search",
            "history_len": "7",
            "is_fullscreen": "false",
            "is_page_visible": "true",
            "keyword": keyword,
            "os": "windows",
            "priority_region": "",
            "referer": "",
            "region": "KR",
            "screen_height": "1080",
            "screen_width": "1920",
            "tz_name": "Asia/Shanghai",
            "web_search_code": "{\"tiktok\":{\"client_params_x\":{\"search_engine\":{\"ies_mt_user_live_video_card_use_libra\":1,\"mt_search_general_user_live_card\":1}},\"search_server\":{}}}",
            "webcast_language": "zh-Hans",
            "msToken": self.cookies["msToken"],
        }
        if cursor != "0":
            params.update({"search_id": search_id})
        x_b = execjs.compile(open('./encrypt.js', encoding='utf-8').read()).call("sign", urlencode(params), self.headers["user-agent"])
        params.update({"X-Bogus": x_b})

        headers = self.headers.copy()
        headers.update({"referer": "https://www.tiktok.com/search?q=" + keyword})

        max_retries = 3
        for attempt in range(max_retries):
            try:
                response = requests.get(
                    url,
                    headers=headers,
                    cookies=self.cookies,
                    params=params,
                    timeout=(3, 10),
                    proxies=None
                )
                return response.json()
            except (ex1, ex2, ex3) as e:
                print(f"尝试 {attempt + 1}/{max_retries} 发生网络错误:{e}")
                if attempt < max_retries - 1:
                    time.sleep(2)
                else:
                    return {"error": f"Network error after {max_retries} attempts: {str(e)}"}
            except Exception as e:
                print(f"发生其他错误:{e}")
                return {"error": str(e)}

3.4 解析数据并存储

解析 TikTok 返回的视频数据,并保存到 CSV 文件。

    def parse_data(self, data_list):
        resultList = []
        video_data = []
        
        for u in data_list:
            try:
                item = u['item']
                author = item['author']
                stats = item['stats']
                author_stats = item['authorStats']  # 添加作者统计信息
                
                # 提取需要的数据
                video_info = {
                    'video_id': item['id'],
                    'desc': item['desc'],
                    'create_time': datetime.fromtimestamp(item['createTime']).strftime('%Y-%m-%d %H:%M:%S'),
                    'duration': item['video']['duration'],
                    # 作者基本信息
                    'author_id': author['id'],
                    'author_name': author['uniqueId'],
                    'author_nickname': author['nickname'],
                    'author_signature': author['signature'],
                    'author_verified': author['verified'],
                    # 作者统计信息
                    'author_following_count': author_stats['followingCount'],  # 关注数
                    'author_follower_count': author_stats['followerCount'],    # 粉丝数
                    'author_heart_count': author_stats['heartCount'],          # 获赞总数
                    'author_video_count': author_stats['videoCount'],          # 视频总数
                    'author_digg_count': author_stats['diggCount'],           # 点赞数
                    # 视频统计信息
                    'digg_count': stats['diggCount'],
                    'share_count': stats['shareCount'],
                    'comment_count': stats['commentCount'],
                    'play_count': stats['playCount'],
                    'collect_count': stats.get('collectCount', 0),
                    'video_url': item['video']['playAddr']
                }
                
                # 添加标签信息
                if 'challenges' in item:
                    video_info['hashtags'] = ','.join([tag['title'] for tag in item['challenges']])
                else:
                    video_info['hashtags'] = ''
                
                # 添加音乐信息
                if 'music' in item:
                    music = item['music']
                    video_info.update({
                        'music_id': music['id'],
                        'music_title': music['title'],
                        'music_author': music['authorName'],
                        'music_original': music['original']
                    })
                
                video_data.append(video_info)
                resultList.append(f"https://www.tiktok.com/@{author['uniqueId']}")
            except Exception as e:
                logger.error(f"解析视频数据时出错: {str(e)}")
                continue
        
        # 将数据保存到CSV文件
        try:
            df = pd.DataFrame(video_data)
            
            # 检查文件是否存在
            file_exists = os.path.exists(self.output_file)
            
            # 如果文件不存在,创建新文件并写入表头
            # 如果文件存在,追加数据不写入表头
            df.to_csv(self.output_file, 
                     mode='a', 
                     header=not file_exists,
                     index=False, 
                     encoding='utf-8-sig')
            
            logger.info(f"数据已{'追加' if file_exists else '保存'}到文件: {self.output_file}")
        except Exception as e:
            logger.error(f"保存CSV文件时出错: {str(e)}")

        return resultList

3.5 运行爬虫

我们定义 main 方法,负责调用 get 方法获取数据并解析。

    def main(self, keyword, cookie_str, cursor="0", search_id=None):
        dataJson = self.get(keyword, cursor, search_id, cookie_str)
        if dataJson:
            if "error" in dataJson:
                return {"cursor": cursor, "search_id": search_id, "data": [], "status": "-2", "error": dataJson["error"]}
            elif "verify_event" in str(dataJson):
                return {"cursor": cursor, "search_id": search_id, "data": [], "status": "-1"}
            else:
                # 解析数据并保存到CSV
                if 'data' in dataJson:
                    self.parse_data(dataJson['data'])
                return dataJson

3.6 运行入口

最后,我们编写 if __name__ == '__main__' 逻辑,定义要爬取的关键词,并进行循环爬取。

if __name__ == '__main__':
    os.makedirs('results1', exist_ok=True)
    topics = [
    "Chen Duxiu",
    "Li Dazhao",
]
    for keyword in topics:
        logger.info(f"开始爬取 {keyword} 的视频")
        output_file = f'results1/{keyword}_videos.csv'  # 你可以自定义文件名
        tiktok = TiktokUserSearch(output_file=output_file)
        cookie_str = '_ttp=2ZzUB37CLclhWsrgyW56Erox1XM; tiktok_webapp_theme_auto_dark_ab=1; delay_guest_mode_vid=5; passport_csrf_token=d8e4d28ec7abdf12a7829d524dca64de; passport_csrf_token_default=d8e4d28ec7abdf12a7829d524dca64de; tt_chain_token=SSmpjX/0in/IP8BYwawD+Q==; multi_sids=7361707798058615814%3A53b730c284c4eaaa9bb2157eef01d70d; cmpl_token=AgQQAPNoF-RO0rYU5JqLsx0__dmghl8Nv5IhYNkWMA; passport_auth_status=a8a7a1e1c4b96a994a45acb38dc83509%2C; passport_auth_status_ss=a8a7a1e1c4b96a994a45acb38dc83509%2C; uid_tt=7857882a3366539dc1d9ca226b3fdc91f76b1b072c7da11dd4120368d88bf861; uid_tt_ss=7857882a3366539dc1d9ca226b3fdc91f76b1b072c7da11dd4120368d88bf861; sid_tt=53b730c284c4eaaa9bb2157eef01d70d; sessionid=53b730c284c4eaaa9bb2157eef01d70d; sessionid_ss=53b730c284c4eaaa9bb2157eef01d70d; store-idc=maliva; store-country-code=ca; store-country-code-src=uid; tt-target-idc=useast1a; tt-target-idc-sign=t3pz21FprSb2qc1ucJWFQbxzCKwgoBX9PKUWEbPHh7_4mpPThOuO0EN9pm2ORzFqk0bLFt6MtI9-gofvcVtQFoGSTOI_JvUWIAAUSHz1mM1A9jP1kRk_qucQnxEMOLvir3s4ffm0hJSh62RyKNO5LBTlT-fsqbi2tQVUwrgIGF-2HFT04S52ciyRnKAXr_0NyD3Aa0lM4J4hUGplo46wKRfId1DwwajXudUfjqJ3rvAuA8qURTsSHCKuDjLbcdfhcC0WKqemrmHFBJ11hGFJxiL4VEOClIoJGrF1_S9jvlx0H0Nph9BHlHNA-wzwi3NF6hPK17WL3TSvsqfEiKclZ5ScpHMv7ATYfOK4BVOzKXrq6fCxzNBT5kCNc4-ImuvjBNqpY8yL2s2KusWxslveOyIq3gwU3Dhxl084w5Tsp13xzuFOGNVHK5ZPeS5ERmykYFB6uTIHty9W_Z6pwN1tT9yQ-34qyZRZB7WONZn_NAFsywU6Hj4wcHLQkJ-tIiAO; last_login_method=google; tiktok_webapp_theme_source=auto; tiktok_webapp_theme=dark; sid_guard=53b730c284c4eaaa9bb2157eef01d70d%7C1740207607%7C15551996%7CThu%2C+21-Aug-2025+07%3A00%3A03+GMT; sid_ucp_v1=1.0.0-KDc2NDMxMTQzMTkwNTY2NTJiOWZhMmZhM2ZlMDg3ZDE0YzNiOGU5NTUKGQiGiIec0MeClWYQ9-vlvQYYsws4CEASSAQQAxoGbWFsaXZhIiA1M2I3MzBjMjg0YzRlYWFhOWJiMjE1N2VlZjAxZDcwZA; ssid_ucp_v1=1.0.0-KDc2NDMxMTQzMTkwNTY2NTJiOWZhMmZhM2ZlMDg3ZDE0YzNiOGU5NTUKGQiGiIec0MeClWYQ9-vlvQYYsws4CEASSAQQAxoGbWFsaXZhIiA1M2I3MzBjMjg0YzRlYWFhOWJiMjE1N2VlZjAxZDcwZA; odin_tt=a7027d0b8a102be6dd20600ca35291f4aee8c003895d8913d5c2f8276f16d6b974345357a22ed0bdcb57930d326afded3e5bd3105e061197876c80a92bbdbbfc29402634b8e7439ba178a1c7ed9ca552; tt_csrf_token=qs6ncqIZ-SbUZVzUbkUZ2SViJyY7VzYRki0M; perf_feed_cache={%22expireTimestamp%22:1742302800000%2C%22itemIds%22:[%227481325874978000134%22%2C%227463463290065161505%22%2C%227466633420098112799%22]}; msToken=BnkIjkPpJEc1i9jiiwT_paC5FW-NL62UVF7-lzpHYki9WIA_KpLrplpY-qlZfuG7V12rbCDHiyQYNrZcOnTzZLk1cvnH3_E_89nfOpqpVquKbSR-Nqr6bGDmL220vjBHdutm4R-gfVnYIG7fvWOJUkZ7yg==; ttwid=1%7Cv5j4n07c_G3ZtA91KIuree-ptnDLwgTwFuM8BnZINnQ%7C1742131441%7Ce88a85fcc36fd7e79815fddb10d16ef553b5e2e4a51c65e2c40098ade19023e2; msToken=iYDKRqCM8rSqc_9ZDzQnWcQiv_iJqPk15-6Y-iFBUmk4uIzb61dM13b9fWHcg4hxGkl9L3n56glok05TllvGurkwpgBYEF8N76ZRIii7OvNEkrk004dagNuoqQVeV9Bzd0_9naXjFXtEiMRi330G5Jdakw==; passport_fe_beating_status=false'
        has_more = 1
        cursor = '0'
        search_id = None
        while has_more:
            data = tiktok.main(keyword, cookie_str, cursor, search_id)
            logger.info(data)
            if data and isinstance(data, dict):
                has_more = data.get('has_more', 0)
                logger.info(has_more)
                cursor = data.get('cursor', '0')
                search_id = data.get('log_pb', {}).get('impr_id')
                if 'data' in data:
                    data = data['data']
                else:
                    logger.error("No data found in response")
                    break
            else:
                logger.error("Invalid response format")
                break
            time.sleep(1)  # 添加延时避免请求过快
        logger.info(f"爬取 {keyword} 的视频完成")
        time.sleep(30)
        logger.info(f"等待30秒后继续爬取下一个主题")
    logger.info("所有主题的视频爬取完成")

4. 关键问题与解决方案

  1. TikTok 反爬措施

    • 需要定期更新 Cookie
    • 适当增加请求间隔
    • 使用代理提高稳定性
  2. X-Bogus 参数

    • 需要使用 JavaScript 计算签名
    • 依赖 encrypt.js 进行加密
  3. 数据存储

    • 使用 pandas 处理数据
    • 追加模式写入 CSV,避免数据丢失

6. 总结

本文介绍了如何使用 Python 爬取 TikTok 用户搜索数据,包括如何构造请求、解析数据并存储到 CSV 文件。希望对有类似需求的读者有所帮助!

帮助与咨询:私信博主或在评论区留言

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2318839.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【HarmonyOS Next】鸿蒙中App、HAP、HAR、HSP概念详解

【HarmonyOS Next】鸿蒙中App、HAP、HAR、HSP概念详解 &#xff08;图1-1&#xff09; 一、鸿蒙中App、HAP、HAR、HSP是什么&#xff1f; &#xff08;1&#xff09;App Pack&#xff08;Application Package&#xff09; 是应用发布的形态&#xff0c;上架应用市场是以App Pa…

计算机二级MS之Excel

声明&#xff1a;跟着大猫和小黑学习随便记下一些笔记供大家参考&#xff0c;二级考试之前将持续更新&#xff0c;希望大家二级都能轻轻松松过啦&#xff0c;过了二级的大神也可以在评论区留言给点建议&#xff0c;感谢大家&#xff01;&#xff01; 文章目录 考题难点&#x…

Unity导出WebGL,无法加载,data文件无法找到 404(NotFound)

问题&#xff1a;data文件无法找到404Not found 示例是使用IIS托管启动 F12可以看到not found 的报错 解决办法&#xff1a; iis无法识别data文件&#xff0c;在MIME类型中增加data 类型&#xff1a;application/octet-stream 添加之后&#xff0c;会在根目录下生产一个…

洛谷题目: P1225 黑白棋游戏 题解 (本题难)

题目传送门&#xff1a; P1225 黑白棋游戏 - 洛谷 (luogu.com.cn) 前言&#xff1a; 这道题要求我们找出从黑白棋游戏的初始棋盘状态变化到目标棋盘状态的最短着棋序列&#xff0c;也就是要找到最少的交换相邻方格棋子的步数以及每一步具体的交换位置。我们可以使用广度优先…

SpringBoot与Redisson整合,用注解方式解决分布式锁的使用问题

文章引用&#xff1a;https://mp.weixin.qq.com/s/XgdKE2rBKL0-nFk2NJPuyg 一、单个服务 1.代码 该接口的作用是累加一个值&#xff0c;访问一次该值加1 RestController public class LockController {Autowiredprivate StringRedisTemplate stringRedisTemplate;GetMappin…

通过Typora + PicGo + 阿里云对象存储(OSS)实现图床

文章目录 通过Typora PicGo 阿里云对象存储&#xff08;OSS&#xff09;实现图床1 准备工作1.1 阿里云对象存储 OSS配置创建oss存储空间bucket获取AccessKey 1.2 PicGo配置1.3 Typora配置 2 使用流程3 常见问题和解决3.1 创建asesskey3.2 You have no right to access this o…

爱普生FC-12M石英晶体谐振器精准时钟源解决方案

在当今数字化时代&#xff0c;电子设备无处不在&#xff0c;从我们日常使用的智能手机、平板电脑&#xff0c;到复杂的工业控制系统、通信基站&#xff0c;每一台设备的稳定运行都离不开精准的时钟信号。而在众多提供时钟信号的元件中&#xff0c;爱普生 FC-12M 石英晶体谐振器…

【css酷炫效果】纯CSS实现手风琴折叠效果

【css酷炫效果】纯CSS实现手风琴折叠效果 缘创作背景html结构css样式完整代码效果图 想直接拿走的老板&#xff0c;链接放在这里&#xff1a;https://download.csdn.net/download/u011561335/90492015 缘 创作随缘&#xff0c;不定时更新。 创作背景 刚看到csdn出活动了&am…

AI辅助的逆向分析

AI大模型结合反编译工具与AI的辅助分析能力&#xff0c;已能实现部分代码逻辑的还原与重构。 1. 技术实现路径 &#xff08;1&#xff09;二进制文件预处理与反编译 反编译工具&#xff1a;需先使用IDA Pro、Ghidra等工具将二进制文件转换为低级中间表示&#xff08;如汇编代…

物理标签与逻辑标签的区别

物理标签和逻辑标签都可以被机器&#xff08;如浏览器、爬虫、屏幕阅读器&#xff09;解析和识别&#xff0c;但它们的 语义信息 对机器的意义不同。以下是详细解释&#xff1a; 1. 物理标签的解析 可以识别&#xff1a;浏览器会正确解析物理标签&#xff08;如 <b>、<…

《Linux 网络架构:基于 TCP 协议的多人聊天系统搭建详解》

一、系统概述 本系统是一个基于 TCP 协议的多人聊天系统&#xff0c;由一个服务器和多个客户端组成。客户端可以连接到服务器&#xff0c;向服务器发送消息&#xff0c;服务器接收到消息后将其转发给其他客户端&#xff0c;实现多人之间的实时聊天。系统使用 C 语言编写&#x…

鸿蒙NEXT项目实战-百得知识库04

代码仓地址&#xff0c;大家记得点个star IbestKnowTeach: 百得知识库基于鸿蒙NEXT稳定版实现的一款企业级开发项目案例。 本案例涉及到多个鸿蒙相关技术知识点&#xff1a; 1、布局 2、配置文件 3、组件的封装和使用 4、路由的使用 5、请求响应拦截器的封装 6、位置服务 7、三…

函数的介绍

1.函数的概念 在C语言中也有函数的概念&#xff0c;有些翻译为&#xff1a;子程序&#xff0c;这种翻译更为准确。C语言的函数就是一个完成某项特定的任务的一小段代码。这段代码是有特殊的写法和调用方法的。 C语言的程序其实是有无数个小的函数组合而成的&#xff0c;也可以…

源自Deformable Convolutional Networks的一种可变形卷积实现解析

衍生记录&#xff1a;深度学习pytorch之简单方法自定义9类卷积即插即用 文章目录 概述1. 可变形卷积的背景2. DeformConv2D概述2.1 构造函数分析2.2 前向传播函数解析2.2.1 偏移量的计算与应用2.2.2 目标位置的计算2.2.3 四个角的插值2.2.4 双线性插值的权重2.2.5 特征图的采样…

【最后203篇系列】020 rocksdb agent

今天还是挺开心的一天&#xff0c;又在工具箱里加了一个工具。嗯&#xff0c;但是快下班的时候也碰到一些不太顺心的事&#xff0c;让我有点恼火。我还真没想到一个专职的前端&#xff0c;加测试&#xff0c;以及其他一堆人&#xff0c;竟然不知道后端返回的markdown,在前端渲染…

mysql-connector-python 报错(0xC0000005)

报错情况&#xff1a; 原因&#xff1a; mysql-connector-python版本不对&#xff0c;我们的mysql版本为sql8.0需要下载mysql-connector-python8.0....的库 方法&#xff1a; pip install mysql-connector-python8.1.0 即可

从零开始实现Stable Diffusion本地部署

1. 依赖安装 文件打包下载地址&#xff08;Stable Diffusion&#xff09; # git &#xff1a; 用于下载源码 https://git-scm.com/downloads/win # Python 作为基础编译环境 https://www.python.org/downloads/ # Nvidia 驱动&#xff0c;用于编译使用GPU显卡硬件 https://ww…

RAG各类方法python源码解读与实践:利用Jupyter对RAG技术综合评测【3万字长文】

检索增强生成&#xff08;RAG &#xff09;是一种结合信息检索与生成模型的混合方法。它通过引入外部知识来提升语言模型的性能&#xff0c;从而提高回答的准确性和事实正确性。为了简单易学&#xff0c;不使用LangChain框架或FAISS向量数据库&#xff0c;而是利用Jupyter Note…

RPA+AI 技术到底好在哪里?

在自动化领域&#xff0c;RPA与生成式AI都是强大的技术&#xff0c;都可以用来实现自动执行重复耗时的任务。 主要区别是&#xff1a;传统RPA擅长处理结构化与规则明确简单的流程&#xff0c;而在非结构化数据处理、动态上下文适应、智能决策等能力上有欠缺&#xff1b;而基于…

flowable适配达梦7 (2.1)

经过第一版的问题解决&#xff0c;后端项目可以启动&#xff0c;前端页面也集成进去。 前端在流程设计页面报错 之后发现主要是组件中modelerStore这个值没有 解决方法:在data增加对象 给component/process/designer.vue 中涉及到的每个子组件传入 :modelerStore“modeler…