Tiktok 关键字 视频及评论信息爬虫(1) [2025.04.07]

news2025/4/18 3:35:46

🙋‍♀️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 = 1
    cursor = '0'
    search_id = None
    request_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")
                break
        else:
            logger.error("Invalid response format")
            break

        time.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 = None
        self.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] = value
        except Exception as e:
            logger.error(f"转换cookie时出错: {str(e)}")
            raise
        return cookie_dict

    def 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 = 3
        for 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 resultList

    def 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.coloradmin.cn/o/2331250.html

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

相关文章

【学习笔记】文件上传漏洞--二次渲染、.htaccess、变异免杀

目录 第十二关 远程包含地址转换 第十三关 突破上传删除 条件竞争 第十四关 二次渲染 第十五关 第十六关 第十七关 .htaccess 第十八关 后门免杀 第十九关 日志包含 第十二关 远程包含地址转换 延续第十一关&#xff0c;加一个文件头&#xff0c;上传成功&#xff0c…

【OS】Process Management(3)

《计算机操作系统&#xff08;第三版&#xff09;》&#xff08;汤小丹&#xff09;学习笔记 文章目录 5、进程通信&#xff08;Inter-Process Communication&#xff09;5.1、进程通信的类型5.1.1、共享存储器系统&#xff08;Shared Memory System&#xff09;5.1.2、消息传递…

单reactor实战

前言&#xff1a;reactor作为一种高性能的范式&#xff0c;值得我们学习 本次目标 实现一个基于的reactor 具备echo功能的服务器 核心组件 Reactor本身是靠一个事件驱动的框架,无疑引出一个类似于moduo的"EventLoop "以及boost.asio中的context而言&#xff0c;不断…

初阶C++笔记第一篇:C++基础语法

虽然以下大多数知识点都在C语言中学过&#xff0c;但还是有一些知识点和C语言不同&#xff0c;比如&#xff1a;代码格式、头文件、关键字、输入输出、字符串类型等... 1. 初识C 1.1 第一个C程序 编写C分为4个步骤&#xff1a; 创建项目创建文件编写代码运行程序 C的第一条…

无需libpacp库,BPF指令高效捕获指定数据包

【环境】无libpacp库的Linux服务器 【要求】高效率读取数据包&#xff0c;并过滤指定端口和ip 目前遇到两个问题 一是手写BPF&#xff0c;难以兼容&#xff0c;有些无法正常过滤二是性能消耗问题&#xff0c;尽可能控制到1% 大方向&#xff1a;过滤数据包要在内核层处理&…

react实现上传图片到阿里云OSS以及问题解决(保姆级)

一、优势 提高上传速度&#xff1a;前端直传利用了浏览器与 OSS 之间的直接连接&#xff0c;能够充分利用用户的网络带宽。相比之下&#xff0c;后端传递文件时&#xff0c;文件需要经过后端服务器的中转&#xff0c;可能会受到后端服务器网络环境和处理能力的限制&#xff0c;…

Python 字典和集合(常见的映射方法)

本章内容的大纲如下&#xff1a; 常见的字典方法 如何处理查找不到的键 标准库中 dict 类型的变种set 和 frozenset 类型 散列表的工作原理 散列表带来的潜在影响&#xff08;什么样的数据类型可作为键、不可预知的 顺序&#xff0c;等等&#xff09; 常见的映射方法 映射类型…

Matlab轴承故障信号仿真与故障分析

1.摘要 本文介绍了一个基于Matlab的轴承故障信号仿真与分析程序&#xff0c;旨在模拟和分析轴承内圈故障信号的特征。程序首先通过生成故障信号、共振信号和调制信号&#xff0c;添加噪声和离散化处理&#xff0c;构建模拟的振动信号&#xff0c;并保存相关数据。通过快速傅里…

Linux 进程 | 概念 / 特征 / 状态 / 优先级 / 空间

注&#xff1a; 本文为 “Linux 进程” 相关文章合辑。 未整理去重。 Linux 进程概念&#xff08;精讲&#xff09; A little strawberry 于 2021-10-15 10:23:55 发布 基本概念 课本概念&#xff1a;程序的一个执行实例&#xff0c;正在执行的程序等。 内核观点&#xff…

重回全面发展亲自操刀

项目场景&#xff1a; 今年工作变动&#xff0c;优化后在一家做国有项目的私人公司安顿下来了。公司环境不如以前&#xff0c;但是好在瑞欣依然可以每天方便的买到。人文氛围挺好&#xff0c;就是工时感觉有点紧&#xff0c;可能长期从事产品迭代开发&#xff0c;一下子转变做项…

3D珠宝渲染用什么软件比较好?渲染100邀请码1a12

印度珠宝商 Mohar Fine Jewels 和英国宝石商 Gemfields 在今年推出了合作珠宝系列——「Emeralds in Full Bloom」&#xff0c;它的灵感源自花草绽放的春季田野&#xff0c;共有 39 件作品&#xff0c;下面这个以植物为主题的开口手镯就是其中一件。 在数字时代&#xff0c;像这…

【数据结构】邻接矩阵完全指南:原理、实现与稠密图优化技巧​

邻接矩阵 导读一、图的存储结构1.1 分类 二、邻接矩阵法2.1 邻接矩阵2.2 邻接矩阵存储网 三、邻接矩阵的存储结构四、算法评价4.1 时间复杂度4.2 空间复杂度 五、邻接矩阵的特点5.1 特点1解析5.2 特点2解析5.3 特点3解析5.4 特点4解析5.5 特点5解析5.6 特点6解析 结语 导读 大…

【嵌入式-stm32电位器控制以及旋转编码器控制LED亮暗】

嵌入式-stm32电位器控制LED亮暗 任务1代码1Key.cKey.hTimer.cTimer.hPWM.cPWM.hmain.c 实验现象1任务2代码2Key.cKey.hmain.c 实验现象2问题与解决总结 源码框架取自江协科技&#xff0c;在此基础上做扩展开发。 任务1 本文主要介绍利用stm32f103C8T6实现电位器控制PWM的占空比…

Uniapp 集成极光推送(JPush)完整指南

文章目录 前言一、准备工作1. 注册极光开发者账号2. 创建应用3. Uniapp项目准备 二、集成极光推送插件方法一&#xff1a;使用UniPush&#xff08;推荐&#xff09;方法二&#xff1a;手动集成极光推送SDK 三、配置原生平台参数四、核心功能实现1. 获取RegistrationID2. 设置别…

2025年常见渗透测试面试题-sql(题目+回答)

网络安全领域各种资源&#xff0c;学习文档&#xff0c;以及工具分享、前沿信息分享、POC、EXP分享。不定期分享各种好玩的项目及好用的工具&#xff0c;欢迎关注。 目录 SQLi 一、发现test.jsp?cid150 注入点的5种WebShell获取思路 1. 文件写入攻击 2. 日志文件劫持 3.…

【RabbitMQ】队列模型

1.概述 RabbitMQ作为消息队列&#xff0c;有6种队列模型&#xff0c;分别在不同的场景进行使用&#xff0c;分别是Hello World&#xff0c;Work queues&#xff0c;Publish/Subscribe&#xff0c;Routing&#xff0c;Topics&#xff0c;RPC。 下面就分别对几个模型进行讲述。…

StarRocks 助力首汽约车精细化运营

作者&#xff1a;任智红&#xff0c;首汽约车大数据负责人 更多交流&#xff0c;联系我们&#xff1a;https://wx.focussend.com/weComLink/mobileQrCodeLink/334%201%202/ffbe5 导读&#xff1a; 本文整理自首汽约车大数据负责人任智红在 StarRocks 年度峰会上的演讲&#xf…

痉挛性斜颈康复助力:饮食调养指南

痉挛性斜颈患者除了积极治疗&#xff0c;合理饮食也能辅助缓解症状&#xff0c;提升生活质量。其健康饮食可从以下方面着手&#xff1a; 高蛋白质食物助力肌肉修复 痉挛性斜颈会导致颈部肌肉异常收缩&#xff0c;消耗较多能量&#xff0c;蛋白质有助于肌肉的修复与维持。日常可…

mysql镜像创建docker容器,及其可能遇到的问题

前提&#xff0c;已经弄好基本的docker服务了。 一、基本流程 1、目录准备 我自己的资料喜欢放在 /data 目录下&#xff0c;所以老规矩&#xff1a; 先进入 /data 目录&#xff1a; cd /data 创建 mysql 目录并进入&#xff1a; mkdir mysql cd mysql 2、镜像查找 docke…

JavaEE——线程的状态

目录 前言1. NEW2. TERMINATED3. RUNNABLE4. 三种阻塞状态总结 前言 本篇文章来讲解线程的几种状态。在Java中&#xff0c;线程的状态是一个枚举类型&#xff0c;Thread.State。其中一共分为了六个状态。分别为&#xff1a;NEW,RUNNABLE,BLOCKED,WAITING,TIMED_WAITING, TERMI…