用 PyQt5 爬虫智能体轻松爬取掘金,自动化采集技术文章和数据

举报
不惑 发表于 2025/07/04 10:41:56 2025/07/04
【摘要】 爬虫技术已经成为了获取互联网上信息的强大工具。从新闻、博客到社交平台的数据抓取,爬虫都能为我们提供大量的有用资源。而今天,我想和大家分享如何利用 CodeBuddy 智能开发工具以及 PyQt5 框架,通过一个智能爬虫系统,自动化地爬取 掘金 上的技术文章和数据。掘金是一个极受开发者欢迎的技术社区,涵盖了最新的技术动态、开发技巧和实战经验。无论你是想了解某个技术的最新文章,还是获取流行技术趋...

爬虫技术已经成为了获取互联网上信息的强大工具。从新闻、博客到社交平台的数据抓取,爬虫都能为我们提供大量的有用资源。而今天,我想和大家分享如何利用 CodeBuddy 智能开发工具以及 PyQt5 框架,通过一个智能爬虫系统,自动化地爬取 掘金 上的技术文章和数据。

掘金是一个极受开发者欢迎的技术社区,涵盖了最新的技术动态、开发技巧和实战经验。无论你是想了解某个技术的最新文章,还是获取流行技术趋势的数据,掘金都提供了丰富的信息资源。那么,如何才能高效且智能地从掘金网站上提取出这些有价值的内容呢?这正是今天我们要探讨的主题。

为什么选择 CodeBuddy 和 PyQt5?

  • CodeBuddy :智能开发助手

CodeBuddy 是一款可以提高开发效率的智能开发工具,它不仅可以自动生成代码,还能提供代码命名、结构优化等辅助功能。通过 CodeBuddy ,我们能够将原本需要花费大量时间的开发工作量,缩短到几分钟以内,极大地提升编写爬虫代码的效率和准确性。尤其在复杂的爬虫项目中,CodeBuddy 能够实时分析爬虫的结构和逻辑,提供合适的建议,帮助我们避免重复劳动和潜在错误。

  • PyQt5:轻量级桌面应用框架

PyQt5 是 Python 中最流行的桌面应用开发框架之一,它支持丰富的 UI 组件,并能够轻松构建跨平台的桌面应用。对于爬虫项目而言,PyQt5 可以帮助我们设计出用户友好的界面,提供灵活的交互方式,从而让整个爬取过程变得更加可视化和直观。更重要的是,PyQt5 可以与爬虫代码进行紧密集成,实现爬虫任务的动态显示与控制。

如何构建基于 PyQt5 的智能爬虫?

  • 确定目标与需求

首先,我们需要明确自己爬取的目标内容。在掘金网站上,我们通常会关注以下几个方面的数据:

  • 热门技术文章

  • 各类专栏的内容

  • 技术话题的最新动态

  • 用户评论和点赞数等互动数据

我们可以根据需求,选择爬取不同类型的页面(如文章列表、文章详情页、评论区等)

  • 设置爬虫智能体

# 网络爬虫专家智能体提示词

## 角色设定 你是一名世界顶尖的网络爬虫专家,精通各类网站数据抓取技术。你拥有10年以上爬虫开发经验,擅长处理反爬机制、动态内容加载、验证码破解等复杂场景。

## 能力范围

- 分析网站结构并设计高效爬取策略

- 处理JavaScript渲染的动态内容

- 绕过常见反爬机制(频率限制、User-Agent检测等)

- 设计分布式爬虫架构

- 数据清洗与存储方案

- 合法合规的爬取建议

## 交互准则

1. 首先询问用户想要爬取的目标网站及具体需求

2. 分析技术可行性并提供多种解决方案

3. 详细解释每种方案的技术细节和潜在挑战

4. 强调合法合规性,提醒用户遵守robots.txt和网站条款

5. 提供代码示例时注明语言和所需库

6. 对复杂任务建议分阶段实施

  • 利用 CodeBuddy 提升开发效率

在开发爬虫时,CodeBuddy 的作用尤为重要。它能够帮助我们自动生成爬虫所需的代码结构,并给出合理的命名建议。例如,当我们定义一个爬虫函数时,CodeBuddy 可以基于函数的功能和上下文,自动生成函数名,减少命名冲突的可能。同时,CodeBuddy 还能分析我们的爬虫逻辑,帮助我们发现潜在的性能瓶颈和优化点。

PyQt5 界面与爬虫集成

在实现爬虫功能的同时,我们还可以利用 PyQt5 构建一个简洁的用户界面,方便我们启动和管理爬虫任务。通过界面上的按钮、进度条、文本框等组件,我们可以:

  • 显示当前爬虫任务的进度

  • 提供动态的控制按钮(例如“开始爬取”、“暂停”、“停止”)

  • 显示爬取的数据统计信息(如成功爬取的文章数量)

这种集成不仅使得爬虫变得更加人性化,还能实时反馈爬取的进度和结果,让我们对爬虫的执行情况一目了然。

爬取掘金的挑战与应对策略

虽然掘金网站内容丰富,但爬虫过程中仍然存在一些挑战:

  • 反爬机制:掘金可能会使用防爬虫技术(如验证码、IP 限制等)来限制频繁请求。为此,我们可以使用代理 IP 和随机请求头来规避反爬。

  • 动态加载数据:掘金网站的内容可能是通过 JavaScript 动态加载的,我们需要使用类似 SeleniumPlaywright 的工具,模拟浏览器行为来抓取动态内容。

  • 数据清洗与处理:爬取的数据往往包含很多噪声信息,需要进一步清洗和整理,以确保其可用性。

指令合集

1. 初始化爬虫项目

指令:初始化一个新的爬虫项目,命名为 Crawler

2. 导入所需库

指令: 导入请求库、HTML解析库和 PyQt5 库。

3. 爬虫目标设定

指令:设置爬虫目标为掘金网站,爬取技术文章、专栏和评论数据。

# Scrapy settings for JuejinCrawler project
#
# For simplicity, this file contains only settings considered important or
# commonly used. You can find more settings consulting the documentation:
#
#     https://docs.scrapy.org/en/latest/topics/settings.html
#     https://docs.scrapy.org/en/latest/topics/downloader-middleware.html
#     https://docs.scrapy.org/en/latest/topics/spider-middleware.html

BOT_NAME = "JuejinCrawler"

SPIDER_MODULES = ["JuejinCrawler.spiders"]
NEWSPIDER_MODULE = "JuejinCrawler.spiders"

ADDONS = {}

# 设置 User-Agent,模拟浏览器访问
USER_AGENT = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36'

# 允许的域名
ALLOWED_DOMAINS = ['juejin.cn']

# 起始 URL
START_URLS = ['https://juejin.cn/']

# Obey robots.txt rules
ROBOTSTXT_OBEY = True

# Configure maximum concurrent requests performed by Scrapy (default: 16)
#CONCURRENT_REQUESTS = 32

# Configure a delay for requests for the same website (default: 0)
# See https://docs.scrapy.org/en/latest/topics/settings.html#download-delay
# See also autothrottle settings and docs
#DOWNLOAD_DELAY = 3
# The download delay setting will honor only one of:
#CONCURRENT_REQUESTS_PER_DOMAIN = 16
#CONCURRENT_REQUESTS_PER_IP = 16

# Disable cookies (enabled by default)
#COOKIES_ENABLED = False

# Disable Telnet Console (enabled by default)
#TELNETCONSOLE_ENABLED = False

# Override the default request headers:
#DEFAULT_REQUEST_HEADERS = {
#    "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
#    "Accept-Language": "en",
#}

# Enable or disable spider middlewares
# See https://docs.scrapy.org/en/latest/topics/spider-middleware.html
#SPIDER_MIDDLEWARES = {
#    "JuejinCrawler.middlewares.JuejincrawlerSpiderMiddleware": 543,
#}

# Enable or disable downloader middlewares
# See https://docs.scrapy.org/en/latest/topics/downloader-middleware.html
#DOWNLOADER_MIDDLEWARES = {
#    "JuejinCrawler.middlewares.JuejincrawlerDownloaderMiddleware": 543,
#}

# Enable or disable extensions
# See https://docs.scrapy.org/en/latest/topics/extensions.html
#EXTENSIONS = {
#    "scrapy.extensions.telnet.TelnetConsole": None,
#}

# Configure item pipelines
# See https://docs.scrapy.org/en/latest/topics/item-pipeline.html
#ITEM_PIPELINES = {
#    "JuejinCrawler.pipelines.JuejincrawlerPipeline": 300,
#}

# Enable and configure the AutoThrottle extension (disabled by default)
# See https://docs.scrapy.org/en/latest/topics/autothrottle.html
#AUTOTHROTTLE_ENABLED = True
# The initial download delay
#AUTOTHROTTLE_START_DELAY = 5
# The maximum download delay to be set in case of high latencies
#AUTOTHROTTLE_MAX_DELAY = 60
# The average number of requests Scrapy should be sending in parallel to
# each remote server
#AUTOTHROTTLE_TARGET_CONCURRENCY = 1.0
# Enable showing throttling stats for every response received:
#AUTOTHROTTLE_DEBUG = False

# Enable and configure HTTP caching (disabled by default)
# See https://docs.scrapy.org/en/latest/topics/downloader-middleware.html#httpcache-middleware-settings
#HTTPCACHE_ENABLED = True
#HTTPCACHE_EXPIRATION_SECS = 0
#HTTPCACHE_DIR = "httpcache"
#HTTPCACHE_IGNORE_HTTP_CODES = []
#HTTPCACHE_STORAGE = "scrapy.extensions.httpcache.FilesystemCacheStorage"

# Set settings whose default value is deprecated to a future-proof value
FEED_EXPORT_ENCODING = "utf-8"

4. 定义数据请求模块

指令:创建一个数据请求模块,发送 GET 请求到掘金网站,并模拟浏览器的 User-Agent

import requests
import random
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.common.by import By
import time

# 用户代理池
USER_AGENTS = [
    'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36',
    'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/15.1 Safari/605.1.15',
    'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
    'Mozilla/5.0 (Windows NT 10.0; WOW64; rv:45.0) Gecko/20100101 Firefox/45.0',
    # 可继续添加更多 User-Agent
]

# 代理池(示例,需替换为真实可用代理)
PROXIES = [
    'http://127.0.0.1:7890',
    # 'http://user:pass@proxyserver:port',
    # 可继续添加更多代理
]

def get_juejin(url, params=None):
    """
    发送 GET 请求到掘金网站,随机使用 User-Agent 和代理。
    :param url: 请求的完整 URL
    :param params: 可选的查询参数字典
    :return: 响应内容(text),如请求失败返回 None
    """
    headers = {
        'User-Agent': random.choice(USER_AGENTS)
    }
    proxies = None
    if PROXIES:
        proxy = random.choice(PROXIES)
        proxies = {
            'http': proxy,
            'https': proxy
        }
    try:
        response = requests.get(url, headers=headers, params=params, proxies=proxies, timeout=10)
        response.raise_for_status()
        return response.text
    except requests.RequestException as e:
        print(f"请求失败: {e}")
        return None

def get_juejin_dynamic(url, wait_time=3, headless=True):
    """
    使用 Selenium 加载掘金网站动态内容。
    :param url: 目标 URL
    :param wait_time: 页面加载等待时间(秒)
    :param headless: 是否无头模式
    :return: 完整渲染后的页面 HTML
    """
    chrome_options = Options()
    if headless:
        chrome_options.add_argument('--headless')
    chrome_options.add_argument('--disable-gpu')
    chrome_options.add_argument('--no-sandbox')
    chrome_options.add_argument(f'user-agent={random.choice(USER_AGENTS)}')
    chrome_options.add_argument('--ignore-certificate-errors')
    chrome_options.add_argument('--ignore-ssl-errors')
    # 如需代理,可添加如下参数:
    # chrome_options.add_argument('--proxy-server=http://127.0.0.1:7890')
    driver = webdriver.Chrome(options=chrome_options)
    try:
        driver.get(url)
        time.sleep(wait_time)  # 等待页面动态内容加载
        html = driver.page_source
        return html
    finally:
        driver.quit() 

5. 定义页面解析模块

指令:创建页面解析模块,使用 BeautifulSoup 提取文章标题、作者和发布时间。

from bs4 import BeautifulSoup

def parse_article(html):
    """
    解析掘金文章页面,提取标题、作者和发布时间。
    :param html: 文章页面的 HTML 内容
    :return: 字典,包含 title、author、publish_time
    """
    soup = BeautifulSoup(html, 'lxml')
    # 标题
    title_tag = soup.find('h1')
    title = title_tag.get_text(strip=True) if title_tag else None
    # 作者
    author_tag = soup.find('a', class_='username')
    author = author_tag.get_text(strip=True) if author_tag else None
    # 发布时间
    time_tag = soup.find('time')
    publish_time = time_tag.get_text(strip=True) if time_tag else None
    return {
        'title': title,
        'author': author,
        'publish_time': publish_time
    } 

6. 存储模块

指令:创建一个存储模块,将爬取的文章数据保存到 CSV 文件中。

import csv
import os

def save_articles_to_csv(articles, filename='articles.csv'):
    """
    将文章数据保存到 CSV 文件。
    :param articles: 文章数据列表,每个元素为字典,包含 title、author、publish_time
    :param filename: 保存的文件名,默认为 articles.csv
    """
    if not articles:
        print("没有可保存的数据。")
        return
    fieldnames = ['title', 'author', 'publish_time']
    file_exists = os.path.isfile(filename)
    with open(filename, 'a', newline='', encoding='utf-8') as csvfile:
        writer = csv.DictWriter(csvfile, fieldnames=fieldnames)
        if not file_exists:
            writer.writeheader()
        for article in articles:
            writer.writerow(article)
    print(f"已保存 {len(articles)} 条数据到 {filename}")

def save_articles_to_txt(articles, filename='articles.txt'):
    """
    将文章数据保存到 TXT 文件,每条数据一行,字段用制表符分隔。
    :param articles: 文章数据列表,每个元素为字典,包含 title、author、publish_time
    :param filename: 保存的文件名,默认为 articles.txt
    """
    if not articles:
        print("没有可保存的数据。")
        return
    fieldnames = ['title', 'author', 'publish_time']
    with open(filename, 'a', encoding='utf-8') as txtfile:
        for article in articles:
            line = '\t'.join(str(article.get(field, '')) for field in fieldnames)
            txtfile.write(line + '\n')
    print(f"已保存 {len(articles)} 条数据到 {filename}") 

7. PyQt5 界面

指令:创建 PyQt5 界面,包含开始、暂停、停止按钮,进度条和日志输出框。

8. 任务调度模块

指令:创建任务调度模块,手动触发爬虫任务的启动与停止。

from PyQt5.QtWidgets import (QWidget, QPushButton, QVBoxLayout, QHBoxLayout, QProgressBar, QTextEdit, QApplication, QLabel)
from PyQt5.QtCore import pyqtSignal
import sys
import threading
import time
from requester import get_juejin_dynamic
from article_parser import parse_article
from cleaner import clean_article
from storage import save_articles_to_txt

class MainWindow(QWidget):
    log_signal = pyqtSignal(str)
    progress_signal = pyqtSignal(int)

    def __init__(self):
        super().__init__()
        self.init_ui()
        self._running = False
        self._thread = None
        self.log_signal.connect(self.log)
        self.progress_signal.connect(self.progress_bar.setValue)

    def init_ui(self):
        # 按钮
        self.start_btn = QPushButton('开始')
        self.pause_btn = QPushButton('暂停')
        self.stop_btn = QPushButton('停止')

        # 进度条
        self.progress_bar = QProgressBar()
        self.progress_bar.setValue(0)

        # 日志输出框
        self.log_output = QTextEdit()
        self.log_output.setReadOnly(True)

        # 布局
        btn_layout = QHBoxLayout()
        btn_layout.addWidget(self.start_btn)
        btn_layout.addWidget(self.pause_btn)
        btn_layout.addWidget(self.stop_btn)

        main_layout = QVBoxLayout()
        main_layout.addLayout(btn_layout)
        main_layout.addWidget(QLabel('进度'))
        main_layout.addWidget(self.progress_bar)
        main_layout.addWidget(QLabel('日志输出'))
        main_layout.addWidget(self.log_output)

        self.setLayout(main_layout)
        self.setWindowTitle('掘金爬虫工具')
        self.resize(500, 400)
        self.start_btn.clicked.connect(self.start_crawl)
        self.stop_btn.clicked.connect(self.stop_crawl)
        self.pause_btn.setEnabled(False)

    def log(self, msg):
        self.log_output.append(msg)
        self.log_output.ensureCursorVisible()

    def start_crawl(self):
        if self._running:
            self.log_signal.emit('爬虫正在运行中...')
            return
        self._running = True
        self.progress_signal.emit(0)
        self.log_signal.emit('开始爬取掘金首页第一篇文章...')
        self._thread = threading.Thread(target=self.crawl_task)
        self._thread.start()

    def stop_crawl(self):
        if not self._running:
            self.log_signal.emit('爬虫未在运行。')
            return
        self._running = False
        self.log_signal.emit('已请求停止爬虫任务。')

    def crawl_task(self):
        try:
            url = 'https://juejin.cn/'
            html = get_juejin_dynamic(url)
            if not html:
                self.log_signal.emit('页面加载失败。')
                self._running = False
                return
            self.progress_signal.emit(20)
            # 解析首页,找到第一篇文章链接
            import lxml.html
            doc = lxml.html.fromstring(html)
            article_links = doc.xpath('//a[contains(@href, "/post/")]/@href')
            if not article_links:
                self.log_signal.emit('未找到文章链接。')
                self._running = False
                return
            first_article_url = 'https://juejin.cn' + article_links[0]
            self.log_signal.emit(f'获取到第一篇文章链接: {first_article_url}')
            self.progress_signal.emit(40)
            article_html = get_juejin_dynamic(first_article_url)
            if not article_html:
                self.log_signal.emit('文章页面加载失败。')
                self._running = False
                return
            self.progress_signal.emit(60)
            article_data = parse_article(article_html)
            if not article_data.get('title'):
                self.log_signal.emit('未能正确解析文章内容。')
                self._running = False
                return
            self.log_signal.emit(f"原始数据: {article_data}")
            cleaned = clean_article(article_data)
            self.log_signal.emit(f"清洗后数据: {cleaned}")
            save_articles_to_txt([cleaned])
            self.progress_signal.emit(100)
            self.log_signal.emit('数据已保存到 articles.txt')
        except Exception as e:
            self.log_signal.emit(f'发生异常: {e}')
        finally:
            self._running = False

if __name__ == '__main__':
    app = QApplication(sys.argv)
    window = MainWindow()
    window.show()
    sys.exit(app.exec_()) 

9. 反爬策略处理

指令:设置代理 IP 和用户代理来避免反爬。

import threading

class CrawlerScheduler:
    def __init__(self, crawl_func):
        """
        :param crawl_func: 爬虫主函数,需为可调用对象
        """
        self.crawl_func = crawl_func
        self._thread = None
        self._running = False

    def start(self, *args, **kwargs):
        if self._running:
            print("爬虫已在运行中。")
            return
        self._running = True
        self._thread = threading.Thread(target=self._run, args=args, kwargs=kwargs)
        self._thread.start()
        print("爬虫任务已启动。")

    def _run(self, *args, **kwargs):
        try:
            self.crawl_func(*args, **kwargs)
        finally:
            self._running = False
            print("爬虫任务已结束。")

    def stop(self):
        if not self._running:
            print("爬虫未在运行。")
            return
        # 这里只能通过设置标志位让爬虫任务自行检查并退出
        self._running = False
        print("已请求停止爬虫任务。请确保爬虫主函数支持中断。")

    def is_running(self):
        return self._running 

10. 动态内容加载处理

指令:使用 Selenium 模拟浏览器来加载掘金网站的动态内容。

11. 清洗与处理爬取数据

指令:创建数据清洗模块,执行去除空值、日期格式化和提取关键词等操作。

import re
from datetime import datetime

from typing import List, Dict

def clean_article(article: Dict) -> Dict:
    """
    清洗单条文章数据:去除空值、日期格式化、提取关键词。
    :param article: 包含 title、author、publish_time 等字段的字典
    :return: 清洗后的字典
    """
    # 去除空值
    cleaned = {k: (v.strip() if isinstance(v, str) else v) for k, v in article.items() if v and str(v).strip()}

    # 日期格式化(假设原始格式为 '2024-05-01 12:34' 或 '2024/05/01' 等)
    if 'publish_time' in cleaned:
        cleaned['publish_time'] = format_date(cleaned['publish_time'])

    # 提取关键词(以标题为例,简单分词,实际可用更强分词工具)
    if 'title' in cleaned:
        cleaned['keywords'] = extract_keywords(cleaned['title'])

    return cleaned

def format_date(date_str: str) -> str:
    """
    尝试将日期字符串格式化为标准 YYYY-MM-DD 格式。
    :param date_str: 原始日期字符串
    :return: 格式化后的日期字符串
    """
    for fmt in ('%Y-%m-%d %H:%M', '%Y-%m-%d', '%Y/%m/%d', '%Y.%m.%d'):
        try:
            dt = datetime.strptime(date_str, fmt)
            return dt.strftime('%Y-%m-%d')
        except Exception:
            continue
    # 若无法识别,原样返回
    return date_str

def extract_keywords(text: str) -> List[str]:
    """
    简单提取关键词(以中文、英文分词为例,实际可用 jieba 等分词库)。
    :param text: 输入文本
    :return: 关键词列表
    """
    # 仅保留中英文、数字
    text = re.sub(r'[^\u4e00-\u9fa5a-zA-Z0-9 ]', ' ', text)
    # 按空格和常见分隔符分词
    words = re.split(r'[\s,,。.!?、]+', text)
    # 去除过短的词
    keywords = [w for w in words if len(w) > 1]
    return keywords 

12. 运行爬虫任务

我们来看一下最终的效果吧

自动化爬取,让技术文章触手可得

通过利用 CodeBuddy 和 PyQt5 的结合,我们可以轻松地开发一个功能强大的爬虫系统,自动化地抓取掘金网站上的技术文章和相关数据。不仅如此,使用 CodeBuddy 能够让我们在开发过程中更加高效、精准,减少代码错误和不必要的重复劳动。最终,我们得到的不仅仅是一个爬虫,而是一个可以高效运营、具备可视化管理界面的智能化爬虫系统,完美符合现代开发者的需求。

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。