网络爬虫学习:应用selenium从搜*狐搜索爬取新闻结果的数据

一、引言

我从24年11月份开始学习网络爬虫应用开发,今年2个来月的努力,于这两天终于完成了开发一款网络爬虫软件的目标。这里对本次软件开发进行一下回顾总结。

在之前的学习中,我是尝试了用requests和BeautifulSoup库来实现爬虫任务,但在测试过程中有部分网站的反爬措施会让爬取任务失败(比如搜*狐),这给我的网络爬虫软件开发造成了很大的麻烦。后来通过不断的学习,发现使用selenium库来进行爬取,使得爬取任务更像是人类的上网浏览行为,能够有效避开这些网站的反爬机制。

之后,我将所有爬虫任务模块代码都重写了一遍,全部改成了用selenium库来实现,今天就用搜*狐作为样板,展示一下学习成果。

二、功能实现

(一)用到的库

本日志中的代码用到了以下几个库:

selenium:‌是一个用于Web应用程序测试的工具,可以模拟真实用户在浏览器中的操作,广泛应用于自动化测试和数据抓取领域。用于实现数据爬取。

queue:提供了线程安全的队列实现,可以有效地解决多线程编程中数据共享和同步的问题。因爬虫任务放在多线程中执行,爬虫的日志信息通过queue传递给主线程。

datetime:是用于处理日期和时间的强大工具。用于获取日期和时间。

time:也是处理与时间相关的库。用到了time.sleep方法实现延迟。

xlsxwriter:用于将爬取的数据保存到xlsx中。

(二)配置 Selenium 浏览器驱动

Selenium支持多种浏览器,我只尝试了Chrome和Edge,发现使用Chrome时,每次打开浏览器都要很长时间,而使用Edge则快很多,因此,我采用了Edge。

    @staticmethod
    def _init_browser():
        """ 配置 Selenium 浏览器驱动 """
        driver = webdriver.Edge()  # 使用Windows自带的Edge浏览器
        return driver

(三)搜*狐*搜索网站的url设置

搜*狐*搜索的网址为'https://search.sohu.com/',经过测试只需要设置keyword参数就可获取到搜索结果,其它的参数可以不用设置,故此,url设置成如下形式即可:

kw = '要搜索的关键字'
url = f'https://search.sohu.com/?keyword={kw}'

(四)搜*狐*搜索结果返回形式

搜*狐*搜索是动态网页,输入关键字点搜索后,会显示10条结果,将浏览器右侧的滑动条往下拖,滑动到页面底部,会刷出新的搜索结果,每拖一次增加10条结果。为了获得更多的结果,需要在浏览器中多拖几次。我在代码中设置了滑动到底部5次,可以获得50条结果,为了防止程序执行过快,结果还没有刷新出来,在执行一次滑动到页面底部操作后就用延迟1.5秒。

另外,初次加载页面也需要等待一段时间,这里使用了wait.until方法,检查id为'new-list-loading'的元素是否出现,以判定页面是否加载完毕。

            # 打开页面
            driver.get(url)
            # 显性等待页面中的搜索结果加载完成
            wait = WebDriverWait(driver, 60)
            wait.until(ec.visibility_of_element_located((By.ID, 'new-list-loading')))  # 此为搜索结果后面跟的元素

            for i in range(5):
                # 滑动到页面底端,执行循环操作是为了尽量多加载搜索结果
                driver.execute_script("window.scrollTo(0, document.body.scrollHeight);")
                time.sleep(1.5)

(五)数据的抓取

通过网页浏览器的开发人员工具对页面数据进行分析,页面中的搜索结果的所在的DIV元素都有‘data-index'和'data-spm-data’,因此通过find_elements方法抓取包含着两个特征的DIV原始就可以获得所有的搜索结果。

# 提取包含搜索结果的关键节点
result_blocks = driver.find_elements(By.CSS_SELECTOR, 'div[data-index][data-spm-data]')

但上述的操作包含了网页源代码中的所有内容,而我们只是需要其中的一些关键数据,如新闻标题、链接、内容摘要、数据来源、发布时间等信息。这就需要对上面操作得到的result_blocks做进一步处理,这里不细说了,可见后面的代码展示。

(六)实现效果

使用selenium来进行爬取的效果如下:

(调用Edge浏览器,自动填入url并执行搜索和爬取操作)

(pycharm中运行本代码的状况)

(结果保存到了xlsx文件中,因省略了部分代码,此展示只抓取了标题和链接信息)

三、代码展示

最后放上完整代码,供参考。

from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as ec
from queue import Queue
from datetime import datetime
import time
import xlsxwriter


class CrawlSohu:
    """ 爬取搜狐搜索的结果 """
    WEBSITE = '搜狐'  # 网站标识
    URL = 'https://search.sohu.com/'
    DELAY_MIN = 2  # 延时最小值
    DELAY_MAX = 5  # 延时最大值

    def __init__(self, queue: Queue):
        self._queue = queue  # Queue对象,用来存放日志信息
        self.mOder = 0  # 序号
        self.mPage = 0  # 页号

    @staticmethod
    def _init_browser():
        """ 配置 Selenium 浏览器驱动 """
        driver = webdriver.Edge()  # 使用Windows自带的Edge浏览器
        return driver

    def crawl_sohu(self, kw: str):
        """
        爬取搜索结果
        :param kw: 关键字
        :return results: 获取到的搜索结果,列表形式
        """
        results = []  # 存储所有搜索结果
        driver = self._init_browser()
        search_para = f"?keyword={kw}"
        url = self.URL + search_para

        self.mPage = 1
        m_now = datetime.now()
        now = m_now.strftime("%Y-%m-%d %H:%M:%S")
        self._queue.put(f'[{now}]: {self.WEBSITE} 开始爬虫任务\n')

        try:
            # 打开页面
            driver.get(url)
            # 显性等待页面中的搜索结果加载完成
            wait = WebDriverWait(driver, 60)
            wait.until(ec.visibility_of_element_located((By.ID, 'new-list-loading')))  # 此为搜索结果后面跟的元素

            for i in range(5):
                # 滑动到页面底端,执行循环操作是为了尽量多加载搜索结果
                driver.execute_script("window.scrollTo(0, document.body.scrollHeight);")
                time.sleep(1.5)

            # 获取页面源代码
            data = self._parse_html_by_selenium(driver)
            results.extend(data)
        except Exception as e:
            print(f"{self.WEBSITE} 发生错误:", e)
        finally:
            driver.quit()  # 关闭浏览器
            print(f"{self.WEBSITE} 采集结束!")

        now = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
        self._queue.put(f'[{now}]: {self.WEBSITE} 任务完成,爬取{self.mPage}页,获取结果{self.mOder}条\n')

        # 返回采集到的所有结果
        return results

    def _parse_html_by_selenium(self, driver: webdriver):
        """
        网页文本分析(selenium)
        :param driver:  webDriver对象
        :return data:  返回的分析结果
        """
        # 提取包含搜索结果的关键节点
        result_blocks = driver.find_elements(By.CSS_SELECTOR, 'div[data-index][data-spm-data]')
        print(f'{self.WEBSITE} 第 {self.mPage} 页 结果数量为:{len(result_blocks)}')

        data = []  # 存放返回结果
        # 提取数据
        for block in result_blocks:
            # 提取内容tag(有两种类型的结构)
            content_tag1 = block.find_elements(By.XPATH, './/div[@class="plain-content"]')
            content_tag2 = block.find_elements(By.XPATH, './/div[@class="cards-content-right"]')
            if content_tag1:
                content_tag = content_tag1[0]
            elif content_tag2:
                content_tag = content_tag2[0]
            else:
                continue
            # 提取标题和链接
            title_link = content_tag.find_elements(By.TAG_NAME, 'a')
            if title_link:
                # 获取标题
                title = title_link[0].text.strip()
                # 获取链接
                link = title_link[0].get_attribute('href')
            else:
                continue
            # 获取内容摘要
            content = ''
            # 省略了具体操作

            # 获取来源和发布日期
            source = ''
            release_time = ''
            # 省略了具体操作

            # 数据清理
            title = title.replace(" ", '')  # 清除空格
            title = title.replace("\n", '')  # 清除换行符

            self.mOder += 1
            # 将本次搜索结果添加到data中
            data.append([self.mOder, self.WEBSITE, title, link, content, source, release_time])
            # 屏幕上输出
            print(self.mOder, ". ", title)
            print(link)
            print("  ")
        return data


def print_logs(s_queue: Queue):
    """
    输出日志
    :param s_queue: 队列,内部存放了爬虫日志
    :return:
    """
    print('输出日志')
    while True:
        if not s_queue.empty():
            data = s_queue.get()
            print(f"{data}")
        else:
            break


def save_data(website, kw, num, datas):
    """
    将数据保存到xlsx文件中
    :param website:  网站标识
    :param kw:  关键字
    :param num:  记录数量
    :param datas:  搜索结果列表
    :return:
    """
    import os
    subdirectory_name = 'results'
    # 检查子目录是否存在,不存在就创建
    if not os.path.exists(subdirectory_name):
        os.makedirs(subdirectory_name)
    # 设置文件名
    file_name = f"{website}-{kw}-数据{num}条.xlsx"
    file_path = os.path.join(subdirectory_name, file_name)
    workbook = xlsxwriter.Workbook(file_path)  # 创建excel
    worksheet = workbook.add_worksheet(f"{website}-{kw}")
    # 创建表头
    worksheet.write(0, 0, "序号")
    worksheet.write(0, 1, "网站")
    worksheet.write(0, 2, "标题")
    worksheet.write(0, 3, "链接")
    worksheet.write(0, 4, "内容摘要")
    worksheet.write(0, 5, "信息来源")
    worksheet.write(0, 6, "时间")
    # 写入数据
    row = 1
    for data in datas:
        worksheet.write(row, 0, data[0])
        worksheet.write(row, 1, data[1])
        worksheet.write(row, 2, data[2])
        worksheet.write(row, 3, data[3])
        worksheet.write(row, 4, data[4])
        worksheet.write(row, 5, data[5])
        worksheet.write(row, 6, data[6])
        row += 1
    # 设置列宽
    worksheet.set_column('C:C', 45)  # 标题列
    worksheet.set_column('D:D', 30)  # 链接列
    worksheet.set_column('E:E', 60)  # 内容摘要列
    worksheet.set_column('F:F', 15)  # 来源列
    worksheet.set_column('G:G', 15)  # 时间列
    # 关闭工作簿
    workbook.close()
    print('数据已保存到xlsx文件中')


if __name__ == '__main__':
    m_kw = '美国Tik Tok难民'  # 设置关键字
    m_queue = Queue()  # 用于传递日志信息
    # 执行爬虫任务
    c_sohu = CrawlSohu(m_queue)  # 创建爬虫任务对象
    m_results = c_sohu.crawl_sohu(m_kw)  # 获取爬虫任务结果,结果为列表形式
    count = len(m_results)
    print(f'\n搜索结果数量:{count}')
    # 打印日志
    print_logs(m_queue)
    # 将爬取的结果保存到xlsx文件中
    save_data('搜狐', m_kw, count, m_results)

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

武陵悭臾

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值