🙋‍♀️Tiktok APP的基于关键字检索的视频及评论信息爬虫共分为两期,希望对大家有所帮助。
第一期见下文。
第二期:基于视频URL的评论信息爬取

1. Node.js环境配置

首先配置 JavaScript 运行环境(如 Node.js),用于执行加密签名代码。
Node.js下载网址:https://nodejs.org/en
Node.js的安装方法(环境配置非常关键,决定了后面的程序是否可以使用):https://blog.csdn.net/liufeifeihuawei/article/details/132425239

2. Py环境配置

import time
import requests
import execjs
import os
from datetime import datetime
from urllib.parse import urlencode
from loguru import logger
import json
import random
from typing import Optional, Dict, List, Any
from concurrent.futures import ThreadPoolExecutor, as_completed
import threading

3. 基于关键字检索的视频信息爬取

1. 主程序:设定爬取的关键字
通过文件topics.csv导入你希望爬取的关键字。
通过文件videosInfo.json存储爬取的结果,以字典格式存储。

if __name__ == '__main__':os.makedirs('../results', exist_ok=True)keywords, fields = read_csv(file_path='topics.csv')  # 设定爬取的关键字output_file = f'../results/videosInfo.json'  # 保存结果的文件cookie_str = read_cookie()# 使用多线程并发爬取with ThreadPoolExecutor(max_workers=1) as executor:futures = []for i in range(len(keywords)):futures.append(executor.submit(crawl_keyword, keywords[i], output_file, cookie_str, fields[i], 20))for future in as_completed(futures):try:future.result()except Exception as e:logger.error(f"爬取过程中发生错误: {str(e)}")logger.info("所有主题的视频爬取完成")

2. 多线程爬取单个关键词,限制最大请求次数
通过request_count 设定爬取的请求次数。

def crawl_keyword(keyword: str, output_file: str, cookie_str: str, field: str, max_requests: int = 10):tiktok = TiktokUserSearch(output_file=output_file)has_more = 1cursor = '0'search_id = Nonerequest_count = 0  # 初始化请求计数器while has_more and request_count < max_requests:data = tiktok.main(keyword, field, cookie_str, cursor, search_id)logger.info(f"Request {request_count + 1}: {data}")if data and isinstance(data, dict):# has_more = data.get('has_more', 0)cursor = data.get('cursor', '0')search_id = data.get('log_pb', {}).get('impr_id')if 'data' in data:data = data['data']request_count += 1  # 更新请求计数else:logger.error("No data found in response")breakelse:logger.error("Invalid response format")breaktime.sleep(random.randint(0, 5))  # 随机延时,避免请求过快write_csv(keyword, request_count, file_path='../results/records.csv')logger.info(f"爬取 {keyword} 的视频完成,共请求 {request_count} 次")

3. 定义TiktokUserSearch类

允许获得24类字段,包括:
🖥️视频的URL、视频时长、标题等;
👨视频的发布者个人简介、获赞数据、视频数据等;
👍视频的点赞信息、分享次数、评论数量、播放次数、收藏次数等;
🎶视频的背景音乐ID,音乐来源等… …

class TiktokUserSearch:def __init__(self, output_file: Optional[str] = None):self.config = read_config()self.headers = self.config.get("headers", {})self.cookies = Noneself.output_file = output_file if output_file else f'tiktok_videos_{datetime.now().strftime("%Y%m%d_%H%M%S")}.csv'self.proxies = self.config.get("proxies", None)  # 代理配置self.lock = threading.Lock()  # 线程锁def cookie_str_to_dict(self, cookie_str: str) -> Dict[str, str]:"""将cookie字符串转换为字典"""cookie_dict = {}try:cookies = [i.strip() for i in cookie_str.split('; ') if i.strip() != ""]for cookie in cookies:key, value = cookie.split('=', 1)cookie_dict[key] = valueexcept Exception as e:logger.error(f"转换cookie时出错: {str(e)}")raisereturn cookie_dictdef get(self, keyword: str, cursor: str, search_id: Optional[str], cookie_str: str) -> Dict[str, Any]:"""发送请求并获取数据"""self.cookies = self.cookie_str_to_dict(cookie_str)url = "https://www.tiktok.com/api/search/general/full/"focus_state = "true" if cursor == "0" else "false"params = {"WebIdLastTime": f"{int(time.time())}","aid": "1988","app_language": "zh-Hans","app_name": "tiktok_web","browser_language": "zh-CN",# ... 略"webcast_language": "zh-Hans","msToken": self.cookies["msToken"],}if cursor != "0":params.update({"search_id": search_id})try:x_b = execjs.compile(open('../configs/encrypt.js', encoding='utf-8').read()).call("sign", urlencode(params),self.headers["user-agent"])params.update({"X-Bogus": x_b})except Exception as e:logger.error(f"生成X-Bogus时出错: {str(e)}")return {"error": str(e)}headers = self.headers.copy()headers.update({"referer": "https://www.tiktok.com/search?q=" + keyword})max_retries = 3for attempt in range(max_retries):try:response = requests.get(url,headers=headers,cookies=self.cookies,params=params,timeout=(3, 10),proxies=self.proxies)response.raise_for_status()return response.json()except (ex1, ex2, ex3) as e:logger.warning(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:logger.error(f"发生其他错误:{e}")return {"error": str(e)}def parse_data(self, data_list: List[Dict[str, Any]], keyword: str, field: str) -> List[str]:"""解析数据并保存到json文件"""resultList = []video_data = []for u in data_list:try:item = u['item']author = item['author']stats = item['stats']author_stats = item['authorStats']video_id = str(item['id']),  # 视频的唯一标识符(TikTok 视频 ID)author_name = str(author['uniqueId']),  # 作者的 TikTok 账号video_url = f'https://www.tiktok.com/@{author_name[0]}/video/{video_id[0]}'video_info = {'search_keyword': keyword,'video_field': field,'video_id': video_id[0],  # 视频的唯一标识符(TikTok 视频 ID)'desc': item['desc'],  # 视频的文字描述(caption/标题)'create_time': datetime.fromtimestamp(item['createTime']).strftime('%Y-%m-%d %H:%M:%S'),  # 视频的发布时间'duration': item['video']['duration'],  # 视频时长(单位:秒)'video_url': video_url,  # 视频播放地址'author_id': author['id'],  # 作者的唯一 ID'author_name': author_name[0],  # 作者的 TikTok 账号(uniqueId,即用户名)#... 略'author_following_count': author_stats['followingCount'],  # 作者关注的人数'digg_count': stats['diggCount'],  # 视频的点赞(like)数量'share_count': stats['shareCount'],  # 视频的分享次数'comment_count': stats['commentCount'],  # 视频的评论数量'play_count': stats['playCount'],  # 视频的播放次数'collect_count': stats.get('collectCount', 0),  # 视频的收藏次数}# video_info['comments'] = self.get_comments(video_url)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# **追加写入 JSON 文件**try:# 如果文件存在,读取已有数据if os.path.exists(self.output_file):with open(self.output_file, 'r', encoding='utf-8') as f:try:existing_data = json.load(f)except json.JSONDecodeError:existing_data = []  # 如果 JSON 解析失败,重置为空列表else:existing_data = []# 追加新数据existing_data.extend(video_data)# 保存回 JSON 文件with open(self.output_file, 'w', encoding='utf-8') as f:json.dump(existing_data, f, ensure_ascii=False, indent=4)logger.info(f"数据已{'追加' if existing_data else '保存'}到文件: {self.output_file}")except Exception as e:logger.error(f"保存 JSON 文件时出错: {str(e)}")return resultListdef main(self, keyword: str, field: str, cookie_str: str, cursor: str = "0", search_id: Optional[str] = None) -> Dict[str, Any]:"""主函数,执行搜索并解析数据"""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:if 'data' in dataJson:self.parse_data(dataJson['data'], keyword, field)return dataJson

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

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【愚公系列】《高效使用DeepSeek》058-选题策划

🌟【技术大咖愚公搬代码:全栈专家的成长之路,你关注的宝藏博主在这里!】🌟 📣开发者圈持续输出高质量干货的"愚公精神"践行者——全网百万开发者都在追更的顶级技术博主! 👉 江湖人称"愚公搬代码",用七年如一日的精神深耕技术领域,以"…

零基础教程:Windows电脑安装Linux系统(双系统/虚拟机)全攻略

一、安装方式选择 方案对比表 特性双系统安装虚拟机安装性能原生硬件性能依赖宿主机资源分配磁盘空间需要独立分区&#xff08;建议50GB&#xff09;动态分配&#xff08;默认20GB起&#xff09;内存占用独占全部内存需手动分配&#xff08;建议4GB&#xff09;启动方式开机选…

LeetCode 2968.执行操作使频率分数最大

给你一个下标从 0 开始的整数数组 nums 和一个整数 k 。 你可以对数组执行 至多 k 次操作&#xff1a; 从数组中选择一个下标 i &#xff0c;将 nums[i] 增加 或者 减少 1 。 最终数组的频率分数定义为数组中众数的 频率 。 请你返回你可以得到的 最大 频率分数。 众数指的…

excel经验

Q:我现在有一个excel&#xff0c;有一列数据&#xff0c;大概两千多行。如何在这一列中 筛选出具有关键字的内容&#xff0c;并输出到另外一列中。 A: 假设数据在A列&#xff08;A1开始&#xff09;&#xff0c;关键字为“ABC”在相邻空白列&#xff08;如B1&#xff09;输入公…

HTTP查询参数示例(XMLHttpRequest查询参数)(带查询参数的HTTP接口示例——以python flask接口为例)flask查询接口

文章目录 HTTP查询参数请求示例接口文档——获取城市列表代码示例效果 带查询参数的HTTP接口示例——以python flask接口为例app.pyREADME.md运行应用API示例客户端示例关键实现说明&#xff1a;运行方法&#xff1a; HTTP查询参数请求示例 接口文档——获取城市列表 代码示例…

将飞帆制作的网页作为 div 集成到自己的网页中

并且自己的网页可以和飞帆中的控件相互调用函数。效果&#xff1a; 上链接 将飞帆制作的网页作为 div 集成到自己的网页中 - 文贝 进入可以复制、运行代码

Redis主从复制:告别单身Redis!

目录 一、 为什么需要主从复制&#xff1f;&#x1f914;二、 如何搭建主从架构&#xff1f;前提条件✅步骤&#x1f4c1; 创建工作目录&#x1f4dc; 创建 Docker Compose 配置文件&#x1f680; 启动所有 Redis&#x1f50d; 验证主从状态 &#x1f4a1; 重要提示和后续改进 …

k8s 1.30.6版本部署(使用canal插件)

#系统环境准备 参考 https://blog.csdn.net/dingzy1/article/details/147062698?spm1001.2014.3001.5501 #配置下载源 curl -fsSL https://mirrors.aliyun.com/kubernetes-new/core/stable/v1.30/deb/Release.key |gpg --dearmor -o /etc/apt/keyrings/kubernetes-apt-keyri…

机器学习的一百个概念(7)独热编码

前言 本文隶属于专栏《机器学习的一百个概念》&#xff0c;该专栏为笔者原创&#xff0c;引用请注明来源&#xff0c;不足和错误之处请在评论区帮忙指出&#xff0c;谢谢&#xff01; 本专栏目录结构和参考文献请见[《机器学习的一百个概念》 ima 知识库 知识库广场搜索&…

RHCSA复习

在Linux中&#xff0c; wrx 分别代表写&#xff08;write&#xff09;、读&#xff08;read&#xff09;和执行&#xff08;execute&#xff09;权限&#xff0c;它们对应的权限值分别是&#xff1a; - r &#xff08;读权限&#xff09;&#xff1a;权限值为4。 - w &am…

“乐企“平台如何重构业财税票全流程生态?

2025年&#xff0c;国家税务总局持续推进的"便民办税春风行动"再次推进数字化服务升级&#xff0c;其中"乐企"平台作为税务信息化的重要载体&#xff0c;持续优化数电票服务能力&#xff0c;为企业提供更高效、更规范的税务管理支持。在这一背景下&#xf…

Android audio(6)-audiopolicyservice介绍

AudioPolicyService 是策略的制定者&#xff0c;比如某种 Stream 类型不同设备的音量&#xff08;index/DB&#xff09;是多少、某种 Stream 类型的音频数据流对应什么设备等等。而 AudioFlinger 则是策略的执行者&#xff0c;例如具体如何与音频设备通信&#xff0c;维护现有系…

Boost库搜索引擎项目(版本1)

Boost库搜索引擎 项目开源地址 Github&#xff1a;https://github.com/H0308/BoostSearchingEngine Gitee&#xff1a;https://gitee.com/EPSDA/BoostSearchingEngine 版本声明 当前为最初版本&#xff0c;后续会根据其他内容对当前项目进行修改&#xff0c;具体见后续版本…

git分支合并信息查看

TortoiseGit工具 1、选择"Revision graph" 2、勾选view中的 Show branchings and merges Arrows point towards merges 3、图案说明 红色部分‌&#xff1a;代表当前分支 橙色部分‌&#xff1a;代表远程分支 黄色部分‌&#xff1a;代表一个tag 绿色部分‌&#xf…

Java学习笔记(多线程):ReentrantLock 源码分析

本文是自己的学习笔记&#xff0c;主要参考资料如下 JavaSE文档 1、AQS 概述1.1、锁的原理1.2、任务队列1.2.1、结点的状态变化 1.3、加锁和解锁的简单流程 2、ReentrantLock2.1、加锁源码分析2.1.1、tryAcquire()的具体实现2.1.2、acquirQueued()的具体实现2.1.3、tryLock的具…

在C++11及后续标准中,auto和decltype是用于类型推导的关键特性,它们的作用和用法。

在C11及后续标准中&#xff0c;auto和decltype是用于类型推导的关键特性&#xff0c;它们的作用和用法有所不同。以下是详细说明&#xff1a; 1. auto 关键字 基本作用 自动推导变量的类型&#xff08;根据初始化表达式&#xff09;主要用于简化代码&#xff0c;避免显式书写…

Linux:进程程序替换execl

目录 引言 1.单进程版程序替换 2.程序替换原理 3.6种替换函数介绍 3.1 函数返回值 3.2 命名理解 3.3 环境变量参数 引言 用fork创建子进程后执行的是和父进程相同的程序(但有可能执行不同的代码分支)&#xff0c;我们所创建的所有的子进程&#xff0c;执行的代码&#x…

LeetCode.02.04.分割链表

分割链表 给你一个链表的头节点 head 和一个特定值 x &#xff0c;请你对链表进行分隔&#xff0c;使得所有 小于 x 的节点都出现在 大于或等于 x 的节点之前。 你不需要 保留 每个分区中各节点的初始相对位置。 示例 1&#xff1a; 输入&#xff1a;head [1,4,3,2,5,2], x …

Johnson算法 流水线问题 java实现

某印刷厂有 6项加工任务J1&#xff0c;J2&#xff0c;J3&#xff0c;J4&#xff0c;J5&#xff0c;J6&#xff0c;需要在两台机器Mi和M2上完 成。 在机器Mi上各任务所需时间为5,1,8,5,3,4单位; 在机器M2上各任务所需时间为7,2,2,4,7,4单位。 即时间矩阵为&#xff1a; T1 {5, …

按键++,--在操作uint8_t类型(一个取值为1~10的数)中,在LCD中显示两位数字问题

问题概况 在执行按键&#xff0c;--过程中&#xff0c;本来数值为1~10.但是在执行过程中&#xff0c;发现数值在经过10数值后&#xff0c;后面的“0”会一直在LCD显示屏中显示。 就是执行操作中&#xff0c;从1&#xff0c;2&#xff0c;3&#xff0c;4&#xff0c;5&#xf…