selenium自动化测试-获取动态页面小说
有的网站页面是动态加载的资源,使用bs4库只能获取静态页面内容,无法获取动态页面内容,通过selenium自动化测试工具可以获取动态页面内容。
参考之前的"bs4库爬取小说工具"文章代码,稍微修改下,就可以转成获取动态页面小说工具。
第一步:先确定目标网址
先找到小说目录页面。
网址首页:'https://www.bq0.net/'
目标小说目录页:'https://www.bq0.net/1bqg/898531870/'
第二步:确定章节目录和内容元素坐标
通过谷歌浏览器F12调试功能可以很快的定位页面元素位置.
第三步:编写代码
采用拆分步骤细化功能模块封装方法编写代码,便于后续扩展功能模块。
requests_webdriver.py:
# -*- coding: UTF-8 -*- # selenium 自动化测试工具,爬取动态网页工具 import requests import time import re from bs4 import BeautifulSoup import random import json import os from selenium import webdriver from selenium.webdriver.chrome.options import Options from selenium.webdriver.common.by import By # 打开驱动 def open_driver(): try: # 连接浏览器web驱动全局变量 global driver # Linux系统下浏览器驱动无界面显示,需要设置参数 # “–no-sandbox”参数是让Chrome在root权限下跑 # “–headless”参数是不用打开图形界面 ''' chrome_options = Options() # 设为无头模式 chrome_options.add_argument('--headless') chrome_options.add_argument('--no-sandbox') chrome_options.add_argument('--disable-gpu') chrome_options.add_argument('--disable-dev-shm-usage') # 连接Chrome浏览器驱动,获取驱动 driver = webdriver.Chrome(chrome_options=chrome_options) ''' # 此步骤很重要,设置chrome为开发者模式,防止被各大网站识别出来使用了Selenium options = Options() # 去掉提示:Chrome正收到自动测试软件的控制 options.add_argument('disable-infobars') # 以键值对的形式加入参数,打开浏览器开发者模式 options.add_experimental_option('excludeSwitches', ['enable-automation']) # 打开浏览器开发者模式 # options.add_argument("--auto-open-devtools-for-tabs") driver = webdriver.Chrome(chrome_options=options) # driver = webdriver.Chrome() print('连接Chrome浏览器驱动') # 浏览器窗口最大化 driver.maximize_window() ''' 1, 隐式等待方法 driver.implicitly_wait(最大等待时间, 单位: 秒) 2, 隐式等待作用 在规定的时间内等待页面所有元素加载; 3,使用场景: 在有页面跳转的时候, 可以使用隐式等待。 ''' driver.implicitly_wait(3) # 强制等待,随机休眠 暂停0-3秒的整数秒,时间区间:[0,3] time.sleep(random.randint(0, 3)) except Exception as e: driver = None print(str(e)) # 关闭驱动 def close_driver(): driver.quit() print('关闭Chrome浏览器驱动') def get_html_by_webdriver(url_str): ''' @方法名称: 根据浏览器驱动获取动态网页内容 @中文注释: 根据浏览器驱动获取动态网页内容 @入参: @param url_str str url地址 @出参: @返回状态: @return 0 失败或异常 @return 1 成功 @返回错误码 @返回错误信息 @作 者: PandaCode辉 @创建时间: 2023-09-21 @使用范例: get_html_by_webdriver('www.baidu.com') ''' try: if (not type(url_str) is str): return [0, "111111", "url地址参数类型错误,不为字符串", [None]] print('浏览器驱动不存在,重新打开浏览器驱动.') # open_driver() # 打开网址网页 driver.get(url_str) # 等待6秒启动完成 driver.implicitly_wait(6) # 获取动态网页的html字符串信息 html_str = driver.page_source print('开始关闭Chrome浏览器驱动') # close_driver() # print(html_str) return [1, '000000', "获取动态网页内容成功", [html_str]] except Exception as e: print("获取动态网页内容异常超时," + str(e)) print('开始关闭Chrome浏览器驱动') close_driver() return [0, '999999', "获取动态网页内容异常超时," + str(e), [None]] def spider_novel_mulu(req_dict): ''' @方法名称: 爬取小说章节目录 @中文注释: 根据参数爬取小说目录,保存到json文件 @入参: @param req_dict dict 请求容器 @出参: @返回状态: @return 0 失败或异常 @return 1 成功 @返回错误码 @返回错误信息 @param rsp_dict dict 响应容器 @作 者: PandaCode辉 @创建时间: 2023-09-21 @使用范例: spider_novel_mulu(req_dict) ''' try: if (not type(req_dict) is dict): return [0, "111111", "请求容器参数类型错误,不为字典", [None]] open_driver() # 根据url地址获取动态网页信息 rst = get_html_by_webdriver(req_dict['mulu_url']) print('随机休眠') # 随机休眠 暂停0-2秒的整数秒 time.sleep(random.randint(0, 2)) close_driver() if rst[0] != 1: return rst html_str = rst[3][0] # 使用BeautifulSoup解析网页数据 soup = BeautifulSoup(html_str, "html.parser") # 目录列表地址 tar_dir_href = soup.select(req_dict['tar_dir_href']) # print(tar_dir_href) tar_len = len(tar_dir_href) print('初始爬取章节总数量:', tar_len) # 过滤章节下标初始化 del_index = 0 for dir_href in tar_dir_href: chap_title = dir_href.text # print(chap_title) if '第1章' in chap_title: break del_index += 1 # 过滤章节下标 print(del_index) # 过滤章节,从第一章开始 tar_dir_href_list = tar_dir_href[del_index:] tar_len = len(tar_dir_href_list) print('过滤后章节总数量:', tar_len) # 目录容器 mulu_dict = {} # 章节标题,列表 mulu_dict['chap_title'] = [] # 章节url,列表 mulu_dict['chap_url'] = [] # 是否完成标志: 0-未完成,1-已完成,列表 mulu_dict['flag'] = [] # 循环读取章节 for dir_href in tar_dir_href_list: # 章节标题 chap_title = dir_href.text print(chap_title) mulu_dict['chap_title'].append(chap_title) # 章节url chap_url = req_dict['novel_url'] + dir_href['href'] print(chap_url) mulu_dict['chap_url'].append(chap_url) mulu_dict['flag'].append('0') # 转换为json字符串 json_str = json.dumps(mulu_dict) json_name = req_dict['novel_name'] + '.json' # 写入json文件 with open(json_name, 'w', encoding="utf-8") as json_file: json_file.write(json_str) # 返回容器 return [1, '000000', '爬取小说目录成功', [None]] except Exception as e: print("爬取小说目录异常," + str(e)) return [0, '999999', "爬取小说目录异常," + str(e), [None]] def spider_novel_content(req_dict): ''' @方法名称: 爬取小说章节明细内容 @中文注释: 读取章节列表json文件,爬取小说章节明细内容,保存到文本文件 @入参: @param req_dict dict 请求容器 @出参: @返回状态: @return 0 失败或异常 @return 1 成功 @返回错误码 @返回错误信息 @param rsp_dict dict 响应容器 @作 者: PandaCode辉 @创建时间: 2023-09-21 @使用范例: spider_novel_content(req_dict) ''' try: if (not type(req_dict) is dict): return [0, "111111", "请求容器参数类型错误,不为字典", [None]] # 章节目录文件名 json_name = req_dict['novel_name'] + '.json' # 检查文件是否存在 if os.path.isfile(json_name): print('json文件存在,不用重新爬取小说目录.') else: print('json文件不存在') # 爬取小说目录 spider_novel_mulu(req_dict) # 读取json文件 with open(json_name, 'r') as f: data_str = f.read() # 转换为字典容器 mulu_dict = json.loads(data_str) """ 关于open()的mode参数: 'r':读 'w':写 'a':追加 'r+' == r+w(可读可写,文件若不存在就报错(IOError)) 'w+' == w+r(可读可写,文件若不存在就创建) 'a+' ==a+r(可追加可写,文件若不存在就创建) 对应的,如果是二进制文件,就都加一个b就好啦: 'rb' 'wb' 'ab' 'rb+' 'wb+' 'ab+' """ file_name = req_dict['novel_name'] + '.txt' # 在列表中查找指定元素的下标,未完成标志下标 flag_index = mulu_dict['flag'].index('0') print(flag_index) # 未完成标志下标为0,则为第一次爬取章节内容,否则已经写入部分,只能追加内容写入文件 # 因为章节明细内容很多,防止爬取过程中间中断,重新爬取,不用重复再爬取之前成功写入的数据 if flag_index == 0: # 打开文件,首次创建写入 fo = open(file_name, "w+", encoding="utf-8") else: # 打开文件,再次追加写入 fo = open(file_name, "a+", encoding="utf-8") # 章节总数 chap_len = len(mulu_dict['chap_url']) # 在列表中查找指定元素的下标 print('章节总数:', chap_len) print('打开浏览器驱动') open_driver() # 循环读取章节 for i in range(flag_index, chap_len): # 章节标题 # chap_title = mulu_dict['chap_title'][i] print('i : ', i) # # 写入文件,章节标题 # fo.write(chap_title + "\r\n") # 章节url chap_url = mulu_dict['chap_url'][i] # 章节分页url列表初始化 page_href_list = [] # 根据url地址获取网页信息 chap_rst = get_html_by_webdriver(chap_url) time.sleep(3) if chap_rst[0] != 1: # 跳出循环爬取 break chap_html_str = chap_rst[3][0] # 使用BeautifulSoup解析网页数据 chap_soup = BeautifulSoup(chap_html_str, "html.parser") # 章节内容分页数和分页url # 获取分页页码标签下的href元素取出 page_href_list_tmp = chap_soup.select("div#PageSet > a") all_page_cnt = len(page_href_list_tmp) print("分页页码链接数量:" + str(all_page_cnt)) # 去除最后/后面数字+.html tmp_chap_url = re.sub(r'(\d+\.html)', '', chap_url) for each in page_href_list_tmp: if len(each) > 0: chap_url = tmp_chap_url + str(each.get('href')) print("拼接小说章节分页url链接:" + chap_url) # 判断是否已经存在列表中 if not chap_url in page_href_list: page_href_list.append(chap_url) print("分页url链接列表:" + str(page_href_list)) # 章节标题 chap_title = chap_soup.select(req_dict['chap_title'])[0].text print(chap_title) # 写入文件,章节标题 fo.write("\n" + chap_title + "\n") # 章节内容 chap_content = chap_soup.select(req_dict['chap_content'])[0].text chap_content = chap_content.replace(' ', '\n').replace(' ', '\n') # print(chap_content) # 写入文件,章节内容 fo.write(chap_content + "\n") # 分页列表大于0 if len(page_href_list) > 0: for chap_url_page in page_href_list: print("chap_url_page:" + chap_url_page) time.sleep(3) # 等待3秒启动完成 driver.implicitly_wait(3) print("等待3秒启动完成") # 根据url地址获取网页信息 chap_rst = get_html_by_webdriver(chap_url_page) if chap_rst[0] != 1: # 跳出循环爬取 break chap_html_str = chap_rst[3][0] # 然后用BeautifulSoup解析html格式工具,简单的获取目标元素 chap_soup_page = BeautifulSoup(chap_html_str, "html.parser") # 章节内容 chap_content_page = chap_soup_page.select(req_dict['chap_content'])[0].text chap_content_page = chap_content_page.replace(' ', '\n').replace(' ', '\n') # print(chap_content_page) # 写入文件,章节内容 fo.write(chap_content_page + "\n") # 爬取明细章节内容成功后,更新对应标志为-1-已完成 mulu_dict['flag'][i] = '1' print('关闭浏览器驱动') close_driver() # 关闭文件 fo.close() print("循环爬取明细章节内容,写入文件完成") # 转换为json字符串 json_str = json.dumps(mulu_dict) # 再次写入json文件,保存更新处理完标志 with open(json_name, 'w', encoding="utf-8") as json_file: json_file.write(json_str) print("再次写入json文件,保存更新处理完标志") # 返回容器 return [1, '000000', '爬取小说内容成功', [None]] except Exception as e: print('关闭浏览器驱动') close_driver() # 关闭文件 fo.close() # 转换为json字符串 json_str = json.dumps(mulu_dict) # 再次写入json文件,保存更新处理完标志 with open(json_name, 'w', encoding="utf-8") as json_file: json_file.write(json_str) print("再次写入json文件,保存更新处理完标志") print("爬取小说内容异常," + str(e)) return [0, '999999', "爬取小说内容异常," + str(e), [None]] # 主方法 if __name__ == '__main__': req_dict = {} '''可变参数,小说名称和对应目录页面url地址''' # 小说名称 req_dict['novel_name'] = '清末的法师' # 目录页面地址,第1页 req_dict['mulu_url'] = 'https://www.bq0.net/1bqg/898531870/' '''下面参数不用变,一个官网一个特定参数配置''' # 官网首页 req_dict['novel_url'] = 'https://www.bq0.net/' # 章节目录定位 req_dict['tar_dir_href'] = "div.box_con>dl>dd>a" # 章节标题定位 req_dict['chap_title'] = 'div.bookname > h1' # 章节明细内容定位 req_dict['chap_content'] = 'div#content' # 爬取小说目录 # spider_novel_mulu(req_dict) # 爬取小说内容 spider_novel_content(req_dict)
第四步:运行测试效果
-------------------------------------------end---------------------------------------