昨天,我朋友想请我帮忙写一个采集抖音视频评论的软件,我在csdn上以及GitHub网站搜了一下,看到了一些方法,特别是接口采集,我自己也用python构建了一个报头试了一下,确实是可以采集出评论,但是由于平台反爬虫限制,仅仅只等采集出一页一小部分,其实如果你会逆向的话,是可以反编译出第二页、第三页...
但是,我放弃了API接口请求的方案,通过我的测试和代码经验,小余给出了这个方案:selenium插件自动化控制Chromedrive
嘿嘿,软件我已放到文章开头,对了,完整的代码我放到文章最后了!可以无脑复制粘贴鸭!
软件界面:
采集效果:
废话不说,直接上干货!
一、原理
首先,我打开了一个抖音链接,滑动评论区,随便找到个评论,鼠标右键,点击检查
我们可以看到网页源代码,准确来说是前端html源码
我复制一个完整的div嵌套,大家可以看一下:
<button type="button" class="J_mtqkIZ comment-reply-expand-btn">...你早说啊 早说我趁年轻下海了 现在我都老了1周前·上海<svg width="20" height="20" fill="none" xmlns="http://www.w3.org/2000/svg" class="LeVwqmMa" viewBox="0 0 20 20"><path fill-rule="evenodd" clip-rule="evenodd" d="M7.646 4.168c-2.238 0-4.035 1.919-4.035 4.152l.002.13a.834.834 0 0 0-.001.073c.006.3.075.66.15.962.074.294.178.622.294.86.467 1.004 1.284 1.979 2.071 2.78a23.69 23.69 0 0 0 2.646 2.323l.015.012.012.01c.174.175.543.54 1.2.54h.019c.186 0 .63 0 1.028-.387l.033-.027c.033-.029.08-.067.14-.117l.003-.003c.436-.359 1.456-1.2 2.462-2.214.644-.646 1.312-1.396 1.822-2.17a7.94 7.94 0 0 0 .2-.318.84.84 0 0 0 .063-.13.956.956 0 0 1 .11-.214.835.835 0 0 0 .074-.144c.029-.073.05-.121.066-.154l.003-.007a.832.832 0 0 0 .147-.29c.125-.444.21-.835.219-1.316a.82.82 0 0 0-.002-.067 5.39 5.39 0 0 0 .002-.16c-.015-2.22-1.807-4.124-4.035-4.124-.845 0-1.667.262-2.316.789a4.029 4.029 0 0 0-2.392-.789zm7.076 4.153V8.424l-.002.07c0 .008 0 .022.002.039a3.065 3.065 0 0 1-.121.721 1.9 1.9 0 0 0-.078.144 3.297 3.297 0 0 0-.089.2c-.083.135-.137.24-.193.38a6.64 6.64 0 0 1-.124.195v.001c-.425.644-1.007 1.305-1.613 1.912l-.002.001a31.607 31.607 0 0 1-2.342 2.106l-.032.026-.12.1-.048-.046c-.05-.05-.119-.105-.152-.131l-.006-.005A22.003 22.003 0 0 1 7.32 11.96l-.003-.003c-.747-.76-1.408-1.577-1.753-2.323a3.149 3.149 0 0 1-.185-.555 3.468 3.468 0 0 1-.1-.553.964.964 0 0 0 0-.104V8.42a3.56 3.56 0 0 1-.001-.099c0-1.373 1.11-2.485 2.368-2.485.662 0 1.288.269 1.848.85a.833.833 0 0 0 1.282-.099c.33-.47.892-.751 1.578-.751 1.258 0 2.368 1.113 2.368 2.485z" fill="#fff" fill-opacity=".7" opacity=".9"></path></svg>6936
<svg width="20" height="20" fill="none" xmlns="http://www.w3.org/2000/svg" class="LeVwqmMa" viewBox="0 0 20 20"><path d="M15.58 8.58c0 .1 0 .21-.01.31v.13l-.01.06c-.04.44-.13.8-.27 1.22-.02.08-.06.15-.12.21h.01c-.04.07-.07.13-.1.19a.37.37 0 0 1-.05.11c-.07.11-.09.14-.13.23-.01.04-.03.07-.05.1-.67 1.09-1.71 2.14-2.56 2.91-.43.39-.83.72-1.12.97-.13.1-.25.19-.33.26l-.03.03-.11.08h-.01l-.01.02h-.01c-.1.11-.33.33-.67.41h-.01c-.09.04-.19.07-.29.08-.58.08-.95-.23-1.11-.37l-.03-.03c-.64-.45-1.27-.93-1.87-1.43-.98-.83-2.16-1.95-2.84-3.13-.14-.24-.26-.5-.36-.77-.11-.29-.2-.59-.25-.89h-.01v-.06l-.02-.14c-.29-2.05.92-4.16 2.92-4.45.15-.02.29-.03.43-.03.88 0 1.7.36 2.41.99.14.13.23.31.23.5l.02 1.29 1.3.96c.14.1.23.26.26.44.03.17-.01.35-.12.5l-.94 1.32.63 1.34c.07.17.08.35.02.51a.69.69 0 0 1-.35.38c-.16.08-.34.09-.51.03a.67.67 0 0 1-.38-.34l-.81-1.7a.702.702 0 0 1-.06-.35c.01-.12.05-.23.12-.33l.79-1.11-1.02-.76a.55.55 0 0 1-.2-.23.641.641 0 0 1-.08-.3l-.02-1.31c-.43-.32-.86-.47-1.28-.47-.08 0-.17 0-.25.02-1.07.15-1.98 1.39-1.77 2.92l.02.1v.02s.01.05.01.08c.03.14.09.36.18.59.09.26.19.46.24.55h.01c.57.98 1.6 1.98 2.55 2.79.54.45 1.09.87 1.66 1.28l.03.02c.07.06.16.12.2.15.03.03.06.05.09.07.03-.01.07-.02.11-.03l.09-.09c.05-.04.13-.11.2-.16l.02-.02.03-.03.32-.26c.36-.29.72-.6 1.07-.91.81-.74 1.72-1.67 2.29-2.58.06-.13.11-.23.19-.36.03-.06.06-.13.09-.19.02-.04.05-.09.09-.14.09-.31.14-.53.17-.78v-.2c.12-1.49-.85-2.63-1.89-2.7h-.12c-.57 0-1.19.26-1.49.68-.05.07-.11.14-.19.18a.55.55 0 0 1-.25.1c-.09.02-.18.02-.27 0a.545.545 0 0 1-.24-.12.54.54 0 0 1-.18-.19.825.825 0 0 1-.1-.26.576.576 0 0 1 .02-.26c.02-.09.06-.17.11-.24.6-.84 1.67-1.25 2.59-1.25.08 0 .15 0 .22.01 1.87.12 3.16 1.92 3.16 3.83z" fill="#fff" fill-opacity=".5"></path></svg>
<svg width="20" height="20" fill="none" xmlns="http://www.w3.org/2000/svg" class="QW9VOhhV" viewBox="0 0 20 20"><path d="M12.066 5.295l-.553.578.553-.578zM10.281 7.3v.8h.8v-.8h-.8zm-.213.002l-.022.8h.035l-.013-.8zM4.823 9.35l.585.545-.585-.545zm-1.821 4.688l.8.024v-.049l-.8.025zm.632.765l-.703-.383.703.383zm6.647-2.056h.8v-.67l-.658-.118-.142.787zm1.754 1.965l.53.6-.53-.6zm4.275-3.776l.53.599-.53-.6zm.03-1.554l.553-.578-.553.578zm-5.259-3.326c0-.224.27-.34.432-.184l1.106-1.156c-1.18-1.13-3.138-.293-3.138 1.34h1.6zm0 1.244V6.057h-1.6v1.244h1.6zm-1 .801l.2-.001V6.5c-.076 0-.152 0-.228.002l.028 1.6zM5.408 9.896c1.605-1.72 3.93-1.813 4.638-1.794l.043-1.6c-.813-.021-3.754.054-5.851 2.303l1.17 1.091zm-1.607 4.118c-.014-.44.096-1.169.373-1.963.275-.79.69-1.572 1.234-2.155l-1.17-1.09c-.738.79-1.25 1.788-1.575 2.718-.322.924-.483 1.853-.461 2.54l1.6-.05zm-.87.407a.463.463 0 0 1 .234-.208.558.558 0 0 1 .485.034c.158.09.187.207.178.177a1.205 1.205 0 0 1-.027-.36l-1.6-.049c-.01.335.02.639.104.9.084.26.247.546.55.72.318.183.66.165.921.056.246-.101.442-.286.56-.504l-1.405-.766zm7.35-1.673l.141-.788h-.002l-.005-.001-.014-.003a3.758 3.758 0 0 0-.232-.034 11.84 11.84 0 0 0-2.684-.053c-1.532.15-3.549.708-4.554 2.552l1.405.766c.61-1.12 1.916-1.59 3.305-1.726a10.234 10.234 0 0 1 2.492.073h.007v.001l.141-.787zm.8 1.174v-1.175h-1.6v1.175h1.6zm.425.192a.255.255 0 0 1-.425-.192h-1.6c0 1.599 1.886 2.449 3.084 1.39l-1.06-1.198zm4.274-3.777l-4.274 3.777 1.059 1.199 4.274-3.777-1.059-1.199zm.007-.376a.255.255 0 0 1-.007.376l1.06 1.199c.813-.72.838-1.98.053-2.731L15.787 9.96zm-4.274-4.088l4.274 4.088 1.106-1.156-4.274-4.088-1.106 1.156z" fill="#fff"></path></svg>分享<svg width="20" height="20" fill="none" xmlns="http://www.w3.org/2000/svg" class="AB4NKfje" viewBox="0 0 36 36"><path fill-rule="evenodd" clip-rule="evenodd" d="M6 17.617C6 11.561 11.578 7 18 7s12 4.561 12 10.617c0 3.036-1.51 5.529-3.749 7.728-.187.184-.381.364-.58.54-1.925 2.082-3.911 3.417-5.847 3.973-.77.221-1.702.242-2.454-.381-.68-.564-.837-1.361-.878-1.823a3.848 3.848 0 0 1-.01-.136C10.898 26.91 6 23.264 6 17.617zM18 10c-5.177 0-9 3.602-9 7.617 0 3.867 3.794 6.992 9.152 6.992h1.5v1.717l-.06.206c0-.001-.03.104-.06.259 1.155-.46 2.515-1.394 3.984-2.996l.056-.06.063-.055c.182-.16.353-.318.514-.475C26.075 21.312 27 19.543 27 17.617 27 13.602 23.177 10 18 10z" fill="#2F3035" fill-opacity=".9"></path></svg>回复展开178条回复<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" focusable="false" class="WxX_Ta2m"><path fill-rule="evenodd" clip-rule="evenodd" d="M3.29289 7.29289C2.90237 7.68342 2.90237 8.31658 3.29289 8.70711L11.2929 16.7071C11.6834 17.0976 12.3166 17.0976 12.7071 16.7071L20.7071 8.70711C21.0976 8.31658 21.0976 7.68342 20.7071 7.29289C20.3166 6.90237 19.6834 6.90237 19.2929 7.29289L12 14.5858L4.70711 7.29289C4.31658 6.90237 3.68342 6.90237 3.29289 7.29289Z" fill="currentColor"></path></svg></button>
可以看到,最外层是
可以看到,每一个标签对应一个主评论
思路也就很显而易见了,我们用selenium自动化控制Chrome浏览器实例,控制页面下滑,将其加载出的html源码提取出来评论者,评论内容,评论者ip地址,汇总爬出即可。
二、代码详解
1. 导入必要的模块
import os
import time
import csv
import tkinter as tk
from tkinter import messagebox, scrolledtext
from selenium import webdriver
from selenium.webdriver.chrome.service import Service
from selenium.webdriver.common.by import By
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.common.action_chains import ActionChains
from selenium.webdriver.common.keys import Keys
from selenium.common.exceptions import (
NoSuchElementException,
StaleElementReferenceException,
WebDriverException
)
import pandas as pd
import threading
这些模块涵盖了文件操作、时间控制、GUI创建、Web浏览器自动化和数据处理等功能,是实现工具所需的基础。
如果你没有安装,可以使用python中的pip下载
pip install selenium pandas
2. 路径设置与检查
路径:
desktop_path = os.path.join(os.path.expanduser("~"), "Desktop")
chrome_driver_path = os.path.join(desktop_path, "抖音采集", "Chrome", "chromedriver.exe")
chrome_exe_path = os.path.join(desktop_path, "抖音采集", "Chrome", "chrome.exe")
user_data_path = os.path.join(desktop_path, "抖音采集", "Chrome", "RENKE", "RENKE1")
icon_path = os.path.join(desktop_path, "抖音采集", "RENKE.ico")
检查:
def check_paths():
missing_files = []
if not os.path.exists(chrome_driver_path):
missing_files.append(f"未找到 ChromeDriver,路径为: {chrome_driver_path}")
if not os.path.exists(chrome_exe_path):
missing_files.append(f"未找到 Chrome.exe,路径为: {chrome_exe_path}")
if not os.path.exists(user_data_path):
missing_files.append(f"未找到用户数据路径,路径为: {user_data_path}")
if not os.path.exists(icon_path):
print(f"警告: 未找到图标文件,路径为: {icon_path}. 将使用默认图标。")
if missing_files:
for msg in missing_files:
messagebox.showerror("错误", msg)
return False
return True
check_paths()
函数用于验证所有必要的文件和路径是否存在。如果缺少关键文件(如ChromeDriver、Chrome浏览器或用户数据路径),则会弹出错误提示,确保工具运行所需的所有资源都已正确配置。
3. 配置Chrome选项
def configure_chrome_options():
chrome_options = Options()
chrome_options.binary_location = chrome_exe_path
chrome_options.add_argument(f"user-data-dir={user_data_path}")
chrome_options.add_argument("--disable-blink-features=AutomationControlled")
chrome_options.add_argument("--disable-infobars")
chrome_options.add_experimental_option("excludeSwitches", ["enable-automation"])
chrome_options.add_experimental_option('useAutomationExtension', False)
return chrome_options
configure_chrome_options()
函数配置了Chrome浏览器的启动选项,包括:
- binary_location:指定Chrome浏览器的可执行文件位置。
- user-data-dir:指定用户数据目录,以保持登录状态。
- disable-blink-features、disable-infobars等参数:减少被检测为自动化工具的可能性,提升脚本的隐蔽性和稳定性。
4. 页面滚动函数
def scroll_down(driver, max_scrolls=30, pause=2, log_callback=None):
actions = ActionChains(driver)
for i in range(max_scrolls):
try:
body = driver.find_element(By.TAG_NAME, "body")
actions.move_to_element(body).click().perform()
body.send_keys(Keys.END)
time.sleep(pause)
if log_callback:
log_callback(f"已下滑 {i+1} 次")
except NoSuchElementException:
if log_callback:
log_callback("未能找到 body 元素,跳过此次滚动。")
continue
except WebDriverException as e:
if log_callback:
log_callback(f"Selenium 异常: {e}")
continue
利用Selenium的功能,脚本模拟用户的滚动行为。具体来说,脚本会定位到页面的
body
元素,模拟点击以激活加载,然后发送按键滚动到页面底部。每次下滑后,脚本会暂停一段时间,以确保新加载的评论能够被正确捕捉。通过用户界面输入的下滑次数,脚本控制滚动的频率和总次数,确保能够加载足够多的评论。
5. 采集评论函数
def scrape_comments(driver, url, scroll_times, log_callback=None):
data = []
try:
driver.get(url)
if log_callback:
log_callback(f"正在加载页面: {url}")
time.sleep(5) # 根据网络情况调整等待时间
scroll_down(driver, max_scrolls=scroll_times, pause=3, log_callback=log_callback)
try:
comments = driver.find_elements(By.CSS_SELECTOR, 'div[data-e2e="comment-item"]')
if log_callback:
log_callback(f"共找到 {len(comments)} 条评论")
except NoSuchElementException:
if log_callback:
log_callback("未找到评论元素。")
comments = []
for comment in comments:
try:
username_element = comment.find_element(By.CSS_SELECTOR, 'a.uz1VJwFY')
username = username_element.text.strip()
comment_content_element = comment.find_element(By.CSS_SELECTOR, 'div.C7LroK_h')
comment_content = comment_content_element.text.strip()
location_element = comment.find_element(By.CSS_SELECTOR, 'div.fJhvAqos > span')
location_text = location_element.text.strip()
if '·' in location_text:
_, location = location_text.split('·', 1)
else:
location = "未知"
data.append({
'视频链接': url,
'评论人': username,
'评论内容': comment_content,
'位置': location
})
except NoSuchElementException:
continue
except StaleElementReferenceException:
continue
except Exception as e:
if log_callback:
log_callback(f"处理 URL 时发生异常: {e}")
return data
在完成页面滚动后,脚本使用CSS选择器定位所有评论元素。对于每个评论元素,脚本会提取以下信息:
- 评论人名称:通过定位特定的链接元素,提取评论人的用户名。
- 评论内容:提取评论的文本内容,包含文字和表情符号。
- 位置信息:提取评论的位置信息,如“1周前·上海”,并从中提取城市名称。
所有提取到的数据会被整理成字典格式,并存储在一个列表中,以便后续的数据处理和保存。
6. 主采集函数
def start_scraping(urls, scroll_times, log_callback=None):
if not check_paths():
return
chrome_options = configure_chrome_options()
service = Service(executable_path=chrome_driver_path)
try:
driver = webdriver.Chrome(service=service, options=chrome_options)
except Exception as e:
messagebox.showerror("错误", f"无法启动 Chrome 浏览器: {e}")
return
all_data = []
for idx, url in enumerate(urls, start=1):
if not url.strip():
continue
if log_callback:
log_callback(f"开始采集第 {idx} 个链接: {url}")
data = scrape_comments(driver, url.strip(), scroll_times, log_callback=log_callback)
all_data.extend(data)
if log_callback:
log_callback(f"完成采集第 {idx} 个链接: {url}")
# 保存到 CSV
if all_data:
csv_file = os.path.join(desktop_path, "抖音评论采集.csv")
try:
df = pd.DataFrame(all_data).drop_duplicates()
df.to_csv(csv_file, index=False, encoding='utf-8-sig')
if log_callback:
log_callback(f"评论已保存到 {csv_file}")
messagebox.showinfo("完成", f"评论已保存到 {csv_file}")
except Exception as e:
messagebox.showerror("错误", f"保存 CSV 文件时发生错误: {e}")
else:
if log_callback:
log_callback("未采集到任何评论。")
messagebox.showwarning("警告", "未采集到任何评论。")
# 关闭浏览器
driver.quit()
start_scraping()
函数是整个采集过程的核心,负责处理多个视频链接。它首先检查必要的文件路径是否存在,配置Chrome选项,并启动Chrome浏览器。然后,它遍历所有输入的链接,调用
scrape_comments()
函数采集每个链接的评论数据,并将所有数据汇总。最后,使用
pandas
将数据保存到CSV文件中,并关闭浏览器。
7. 用户界面创建
def create_gui():
root = tk.Tk()
root.title("壬科科技 软件仅用于测试学习,禁止一切非法用途")
# 设置图标
if os.path.exists(icon_path):
try:
root.iconbitmap(icon_path)
except Exception as e:
print(f"设置图标时发生异常: {e}")
root.geometry("700x700")
# 标签
label = tk.Label(root, text="请输入抖音视频链接(每行一个):")
label.pack(pady=10)
# 文本框
text_box = scrolledtext.ScrolledText(root, width=80, height=15)
text_box.pack(pady=10)
# 滑动次数标签和输入框
scroll_label = tk.Label(root, text="请输入页面下滑次数(默认30):")
scroll_label.pack(pady=5)
scroll_entry = tk.Entry(root, width=10)
scroll_entry.insert(0, "30") # 默认值为30
scroll_entry.pack(pady=5)
# 日志框
log_label = tk.Label(root, text="日志输出:")
log_label.pack(pady=5)
log_box = scrolledtext.ScrolledText(root, width=80, height=15, state='disabled')
log_box.pack(pady=5)
# 记录日志的函数
def log(message):
log_box.configure(state='normal')
log_box.insert(tk.END, message + "\n")
log_box.configure(state='disabled')
log_box.see(tk.END)
# 开始按钮的回调函数
def on_start():
urls = text_box.get("1.0", tk.END).strip().split('\n')
scroll_times_str = scroll_entry.get().strip()
if not urls or all(not url.strip() for url in urls):
messagebox.showwarning("警告", "请在文本框中输入至少一个抖音视频链接。")
return
try:
scroll_times = int(scroll_times_str)
if scroll_times <= 0:
raise ValueError
except ValueError:
messagebox.showwarning("警告", "请输入一个有效的正整数作为下滑次数。")
return
start_button.config(state='disabled')
root.update()
# 在新线程中运行采集,以避免阻塞 GUI
def run_scraping():
start_scraping(urls, scroll_times, log_callback=log)
start_button.config(state='normal')
threading.Thread(target=run_scraping).start()
# 开始按钮
start_button = tk.Button(root, text="开始采集", command=on_start, width=20, bg="green", fg="white")
start_button.pack(pady=10)
root.mainloop()
create_gui()
函数负责创建用户界面,具体组件包括:
- 主窗口:使用
Tkinter
创建一个主窗口,并设置窗口标题和图标。 - 输入框:一个滚动文本框,用户可以在此处粘贴多个抖音视频链接,每行一个。
- 下滑次数输入框:一个文本输入框,用户可以输入希望页面下滑的次数,默认值为30。
- 日志输出框:一个滚动文本框,用于实时显示采集过程中的日志信息。
- 开始采集按钮:点击后启动采集过程。为了确保界面在采集过程中保持响应,采集任务在一个独立的线程中运行。
8. 程序入口
if __name__ == "__main__":
create_gui()
如果大家做过卡密系统的都知道,这个地方可是设置一个门,定义一个检查函数,用于验证用户信息,单机单卡等等,这里我便不再赘述!
三、完整代码
完整代码我放这了,嘿嘿!こん ✿(。◕‿◕。)✿
import os
import time
import csv
import tkinter as tk
from tkinter import messagebox, scrolledtext
from selenium import webdriver
from selenium.webdriver.chrome.service import Service
from selenium.webdriver.common.by import By
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.common.action_chains import ActionChains
from selenium.webdriver.common.keys import Keys
from selenium.common.exceptions import (
NoSuchElementException,
StaleElementReferenceException,
WebDriverException
)
import pandas as pd
import threading
# 设置路径
desktop_path = os.path.join(os.path.expanduser("~"), "Desktop")
chrome_driver_path = os.path.join(desktop_path, "抖音采集", "Chrome", "chromedriver.exe")
chrome_exe_path = os.path.join(desktop_path, "抖音采集", "Chrome", "chrome.exe")
user_data_path = os.path.join(desktop_path, "抖音采集", "Chrome", "RENKE", "RENKE1")
icon_path = os.path.join(desktop_path, "抖音采集", "RENKE.ico")
# 检查必要文件是否存在
def check_paths():
missing_files = []
if not os.path.exists(chrome_driver_path):
missing_files.append(f"未找到 ChromeDriver,路径为: {chrome_driver_path}")
if not os.path.exists(chrome_exe_path):
missing_files.append(f"未找到 Chrome.exe,路径为: {chrome_exe_path}")
if not os.path.exists(user_data_path):
missing_files.append(f"未找到用户数据路径,路径为: {user_data_path}")
if not os.path.exists(icon_path):
print(f"警告: 未找到图标文件,路径为: {icon_path}. 将使用默认图标。")
if missing_files:
for msg in missing_files:
messagebox.showerror("错误", msg)
return False
return True
# 配置 Chrome 选项
def configure_chrome_options():
chrome_options = Options()
chrome_options.binary_location = chrome_exe_path
chrome_options.add_argument(f"user-data-dir={user_data_path}")
chrome_options.add_argument("--disable-blink-features=AutomationControlled")
chrome_options.add_argument("--disable-infobars")
chrome_options.add_experimental_option("excludeSwitches", ["enable-automation"])
chrome_options.add_experimental_option('useAutomationExtension', False)
return chrome_options
# 滚动页面加载更多评论,最多下滑指定次数
def scroll_down(driver, max_scrolls=30, pause=2, log_callback=None):
actions = ActionChains(driver)
for i in range(max_scrolls):
try:
# 找到 body 元素
body = driver.find_element(By.TAG_NAME, "body")
# 模拟点击页面以激活加载
actions.move_to_element(body).click().perform()
# 滚动页面到底部
body.send_keys(Keys.END)
time.sleep(pause)
if log_callback:
log_callback(f"已下滑 {i+1} 次")
except NoSuchElementException:
if log_callback:
log_callback("未能找到 body 元素,跳过此次滚动。")
continue
except WebDriverException as e:
if log_callback:
log_callback(f"Selenium 异常: {e}")
continue
# 采集单个视频的评论
def scrape_comments(driver, url, scroll_times, log_callback=None):
data = []
try:
driver.get(url)
if log_callback:
log_callback(f"正在加载页面: {url}")
# 等待页面加载
time.sleep(5) # 根据网络情况调整等待时间
# 开始滚动页面
scroll_down(driver, max_scrolls=scroll_times, pause=3, log_callback=log_callback)
# 找到所有评论元素
try:
comments = driver.find_elements(By.CSS_SELECTOR, 'div[data-e2e="comment-item"]')
if log_callback:
log_callback(f"共找到 {len(comments)} 条评论")
except NoSuchElementException:
if log_callback:
log_callback("未找到评论元素。")
comments = []
for comment in comments:
try:
# 获取评论人名称
username_element = comment.find_element(By.CSS_SELECTOR, 'a.uz1VJwFY')
username = username_element.text.strip()
# 获取评论内容
comment_content_element = comment.find_element(By.CSS_SELECTOR, 'div.C7LroK_h')
comment_content = comment_content_element.text.strip()
# 获取位置信息(假设位置信息在特定的 span 中,如 '1周前·上海',提取城市)
location_element = comment.find_element(By.CSS_SELECTOR, 'div.fJhvAqos > span')
location_text = location_element.text.strip()
# 分割获取城市信息
if '·' in location_text:
_, location = location_text.split('·', 1)
else:
location = "未知"
data.append({
'视频链接': url,
'评论人': username,
'评论内容': comment_content,
'位置': location
})
except NoSuchElementException:
continue
except StaleElementReferenceException:
continue
except Exception as e:
if log_callback:
log_callback(f"处理 URL 时发生异常: {e}")
return data
# 主采集函数
def start_scraping(urls, scroll_times, log_callback=None):
if not check_paths():
return
chrome_options = configure_chrome_options()
service = Service(executable_path=chrome_driver_path)
try:
driver = webdriver.Chrome(service=service, options=chrome_options)
except Exception as e:
messagebox.showerror("错误", f"无法启动 Chrome 浏览器: {e}")
return
all_data = []
for idx, url in enumerate(urls, start=1):
if not url.strip():
continue
if log_callback:
log_callback(f"开始采集第 {idx} 个链接: {url}")
data = scrape_comments(driver, url.strip(), scroll_times, log_callback=log_callback)
all_data.extend(data)
if log_callback:
log_callback(f"完成采集第 {idx} 个链接: {url}")
# 保存到 CSV
if all_data:
csv_file = os.path.join(desktop_path, "抖音评论采集.csv")
try:
df = pd.DataFrame(all_data).drop_duplicates()
df.to_csv(csv_file, index=False, encoding='utf-8-sig')
if log_callback:
log_callback(f"评论已保存到 {csv_file}")
messagebox.showinfo("完成", f"评论已保存到 {csv_file}")
except Exception as e:
messagebox.showerror("错误", f"保存 CSV 文件时发生错误: {e}")
else:
if log_callback:
log_callback("未采集到任何评论。")
messagebox.showwarning("警告", "未采集到任何评论。")
# 关闭浏览器
driver.quit()
# 创建用户界面
def create_gui():
root = tk.Tk()
root.title("壬科科技 软件仅用于测试学习,禁止一切非法用途")
# 设置图标
if os.path.exists(icon_path):
try:
root.iconbitmap(icon_path)
except Exception as e:
print(f"设置图标时发生异常: {e}")
root.geometry("700x700")
# 标签
label = tk.Label(root, text="请输入抖音视频链接(每行一个):")
label.pack(pady=10)
# 文本框
text_box = scrolledtext.ScrolledText(root, width=80, height=15)
text_box.pack(pady=10)
# 滑动次数标签和输入框
scroll_label = tk.Label(root, text="请输入页面下滑次数(默认30):")
scroll_label.pack(pady=5)
scroll_entry = tk.Entry(root, width=10)
scroll_entry.insert(0, "30") # 默认值为30
scroll_entry.pack(pady=5)
# 日志框
log_label = tk.Label(root, text="日志输出:")
log_label.pack(pady=5)
log_box = scrolledtext.ScrolledText(root, width=80, height=15, state='disabled')
log_box.pack(pady=5)
# 记录日志的函数
def log(message):
log_box.configure(state='normal')
log_box.insert(tk.END, message + "\n")
log_box.configure(state='disabled')
log_box.see(tk.END)
# 开始按钮的回调函数
def on_start():
urls = text_box.get("1.0", tk.END).strip().split('\n')
scroll_times_str = scroll_entry.get().strip()
if not urls or all(not url.strip() for url in urls):
messagebox.showwarning("警告", "请在文本框中输入至少一个抖音视频链接。")
return
try:
scroll_times = int(scroll_times_str)
if scroll_times <= 0:
raise ValueError
except ValueError:
messagebox.showwarning("警告", "请输入一个有效的正整数作为下滑次数。")
return
start_button.config(state='disabled')
root.update()
# 在新线程中运行采集,以避免阻塞 GUI
def run_scraping():
start_scraping(urls, scroll_times, log_callback=log)
start_button.config(state='normal')
threading.Thread(target=run_scraping).start()
# 开始按钮
start_button = tk.Button(root, text="开始采集", command=on_start, width=20, bg="green", fg="white")
start_button.pack(pady=10)
root.mainloop()
# 入口点
if __name__ == "__main__":
create_gui()
本期内容到此就告一段落了
可以为小余点个小心心吗
我们下期见
⁞ つ: •̀ ⌂ •́ : ⁞-︻╦̵̵͇̿̿̿̿══╤─
版权归原作者 RENKEKJI 所有, 如有侵权,请联系我们删除。