一、引言
我从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)

2149

被折叠的 条评论
为什么被折叠?



