Python - 第三方HTTP库Requests使用详解5(BeautifulSoup、网页爬虫)
作者:hangge | 2022-06-30 09:11
六、使用 BeautifulSoup 解析 HTML 页面代码
1,基本介绍
简单来说,Beautiful Soup 就是 Python 的一个 HTML 或 XML 的解析库,使用它可以很方便地从网页中提取数据。
- Beautiful Soup 提供一些简单的、Python 式的函数来处理导航、搜索、修改分析树等功能。它是一个工具箱,通过解析文档为用户提供需要抓取的数据,因为简单,所以不需要多少代码就可以写出一个完整的应用程序。
- Beautiful Soup 自动将输入文档转换为 Unicode 编码,输出文档转换为 UTF-8 编码。你不需要考虑编码方式,除非文档没有指定一个编码方式,这时你仅仅需要说明一下原始编码方式就可以了。
- Beautiful Soup 已成为和 lxml、html6lib 一样出色的 Python 解释器,为用户灵活地提供不同的解析策略或强劲的速度。
2,基本用法
(1)通过标签查找对象
soup.a 和 soup.find('a') #查找第一个a标签,返回值就是一个tag对象。 soup.find('a', {'class':'title abc'}) #查找第一个css的class为title abc的a标签,返回值就是一个tag对象。 soup.find_all('a') #查找所有的a标签,返回一个tag对象集合的list。 soup.find_all('a', class='name') #查找class这个属性为name的a标签,返回一个tag对象集合的list。 soup.find_all('a', limit=3) #查找三个a标签,返回一个tag对象集合的list。 soup.find_all(text="") #查找text为某个字符串的结果,返回一个tag对象集合的list。 soup.find_all(["p", "span"]) # 查找p节点和span节点 soup.find_all(class_=re.compile("^p")) # 正则表达式(查找class属性值以p开头的节点) #其他find方法 find_parent #查找父节点 find_parents #递归查找父节点 find_next_siblings #查找后面的兄弟节点 find_next_sibling #查找后面满足条件的第一个兄弟节点 find_all_next #查找后面所有节点 find_next #查找后面第一个满足条件的节点 find_all_previous #查找前面所有满足条件的节点 find_previous #查找前面第一个满足条件的节点
(2)通过 CSS 选择器查找对象
# select 与 select_one soup.select("a") #查找所有的a标签,返回一个tag对象集合的list soup.select_one("a") #查找第一个a标签,返回一个tag对象 # 通过tag选择 soup.select("title") # 选择title节点 soup.select("body a") # 选择body节点下的所有a节点 soup.select("html head title") # 选择html节点下的head节点下的title节点 # id与类选择器 soup.select(".article") # 选择类名为article的节点 soup.select("a#id1") # 选择id为id1的a节点 soup.select("#id1") # 选择id为id1的节点 soup.select("#id1,#id2") # 选择id为id1、id2的节点 # 属性选择器 soup.select('a[href]') # 选择有href属性的a节点 soup.select('a[href="http://hangge.com/get"]') # 选择href属性为http://hangge.com/get的a节点 soup.select('a[href^="http://hangge.com/"]') # 选择href以http://hangge.com/开头的a节点 soup.select('a[href$="png"]') # 选择href以png结尾的a节点 soup.select('a[href*="china"]') # 选择href属性包含china的a节点 soup.select("a[href~=china]") # 选择href属性包含china的a节点 #其他选择器 soup.select("div > p") # 父节点为div节点的p节点 soup.select("div + p") # 节点之前有div节点的p节点 soup.select("p~ul") # p节点之后的ul节点(p和ul有共同父节点) soup.select("p:nth-of-type(3)") # 父节点中的第3个p节点
(3)获取对象内容
soup.a['class'] 和 soup.a.get['class'] #获取第一个a标签的class属性值。 soup.a.name #获取第一个a标签的名字。 soup.a.string #获取第一个a标签内非属性的字符串,如果是在内的非属性字符串,则必须是通过soup.a.get_text()获取 soup.a.strings #获取a标签内非属性的所有的字符串列表。 soup.a.stripped_strings #获取a标签内非属性的所有的字符串列表,去除空白和空行。 soup.a.text 和 soup.a.get_text() #获取第一个a标签内非属性的字符串。例如∶ abc_()获取的是abc。 soup.a.attrs #获取第一个a标签的所有属性。
3,使用样例
(1)下面样例我们要抓取 csdn 首页的头条热点条目:
(2)查看页面源代码可以发现,各个条目标题都在 class 为 headswiper-item 的 div 下的第 1 个 a 标签中:
(3)下面是具体的代码:
import requests from bs4 import BeautifulSoup response = requests.get("https://www.csdn.net") soup = BeautifulSoup(response.text, "lxml") items = soup.select(".headswiper-item") for item in items: print(item.a.text)
附:网页爬虫
1,准备工作
(1)这里我们已抓取并下载起点小说网上的小说作为演示。首先找一部需要下载的小说,记下该小说的 bookId。
(3)我们访问如下接口传入前面获取的 csrfToken 和 bookId 即可获取整个小说的目录。其中 vs 为所有的卷集合,每个卷下的 cs 为该卷所有的章节集合。
(4)每个章节的 cU 即为该章节页面的地址,我们拼接上固定的前缀即可访问该页面,接下来只要解析该页面内容,并保存到本地即可。
2,样例代码
(1)下面是爬虫完整代码,使用时只需要根据情况修改下 bookId、csrfToken 以及下载文件保存地址即可:
import requests import re from bs4 import BeautifulSoup from requests.exceptions import * import random import json import time import os import sys # 随机返回list中的某个User Agent设置值,防止被禁 def get_user_agent(): list = ['Mozilla/5.0(Windows NT 10.0;Win64; x64)AppleWebKit/537.36(KHTML,like Gecko) Chrome/87.0.4280.66 Safari/537.36', 'Mozilla/5.0(Windows NT 6.3;Win64;x64)AppleWebKit/537.36 (KHTML,like Gecko) Chrome/70.0.3538.77 Safari/537.36', 'Mozilla/5.0(Windows NT 6.2; Win64;x64) AppleWebKit/537.36(KHTML,like Gecko) Chrome/70.0.3538.77 Safari/537.36', 'Mozilla/5.0(Windows NT 6.1;Win64;x64AppleWebKit/537.36 (KHTML,like GeckoChrome/70.0.3538.77 Safari/537.36', 'Mozilla/5.0(Windows NT 6.3;WOW64) AppleWebKit/537.36(KHTML,like Gecko)Chrome/41.0.2225.0 Safari/537.36', 'Mozilla/5.0(Windows NT 6.2; WOW64) AppleWebKit/537.36(KHTML,like Gecko)Chrome/41.0.2225.0 Safari/537.36', 'Mozilla/5.0(Windows NT6.1; WOW64)AppleWebKit/537.36(KHTML, like Gecko)Chrome/41.0.2225.0 Safari/537.36'] return list[random.randint(0, len(list)-1)] # 返回url网址上书籍的卷章(Page)id,每个卷章(Page)对应的章节(Chap)数目,以及章节页面地址 def getPageAndChapUrl(bookId, csrfToken): url ='https://book.qidian.com/ajax/book/category?_csrfToken=' + csrfToken + '&bookId=' + bookId headers ={ 'User-Agent': get_user_agent(), 'Referer':'https://book.qidian.com/info/' + bookId } try: response = requests.get(url=url, params=headers) if response.status_code == 200: json_str = response.text list = json.loads(json_str)['data']['vs'] volume ={ 'VolumeId_List':[], 'VolumeNum_List':[], 'ChapterUrl_List':[] } for i in range(len(list)): json_str = json.dumps(list[i]).replace(" ","") volume_id =re.search('.*?"vId":(.*?),',json_str,re.S).group(1) volume_num=re.search('.*?"cCnt":(.*?),',json_str,re.S).group(1) volume['VolumeId_List'].append(volume_id) volume['VolumeNum_List'].append(volume_num) volume['ChapterUrl_List'].append([]) for j in range(len(list[i]['cs'])): volume['ChapterUrl_List'][i].append(list[i]['cs'][j]['cU']) print(volume) return volume else: print('No response') return None except Exception as e: print("请求页面出错!", e) return None #通过每个章节的页面地址找到要爬取的页面,并返回页面html信息。 def getPage(savePath, volume, bookId): for i in range(len(volume['VolumeId_List'])): path = savePath + '/第'+ str(i + 1) + '卷_共'+ volume['VolumeNum_List'][i] +'章' mkdir(path) print('--- 第' + str(i+1) +'卷已开始爬取 ---') for j in range(int(volume['VolumeNum_List'][i])): url ='https://read.qidian.com/chapter/' + volume['ChapterUrl_List'][i][j] print('正在爬取第' + str(i+1) + '卷第' + str(j+1) + '章路径:'+url) headers ={ 'User-Agent': get_user_agent(), 'Referer':'https://book.qidian.com/info/' + bookId } try: response = requests.get(url=url, params=headers) if(response.status_code==200): getChapAndSavetxt(response.text, url, path, j) else: print ('No response') return None except ReadTimeout: print("ReadTimeout!") return None except RequestException: print("请求页面出错!") return None time.sleep(5) print('--- 第' + str(i+1) +'卷爬取结束 ---') #解析小说内容页面,将每章节(Chap)内容写入txt文件,并存储到相应的卷章(Page)目录下。 # 其中,html为小说内容页面;url为访问路径;path为卷章存储路径;chapNum为每个卷章Page对应的章节Chap数目 def getChapAndSavetxt(html, url, path, chapNum): if html == None: print('访问路径为'+url+'的页面为空') return soup = BeautifulSoup(html,'lxml') ChapName = soup.find('h3',attrs={'class':'j_chapterName'}).span.string ChapName = re.sub('[\/:*?"<>]','',ChapName) filename = path+'//'+'第'+ str(chapNum+1) +'章.' + ChapName + '.txt' readContent = soup.find('div', attrs={'class':'read-content j_readContent'}).find_all('p') paragraph = [] for item in readContent: paragraph.append(re.search('.*?<p>(.*?)</p>',str(item),re.S).group(1)) save2file(filename, '\n'.join(paragraph)) # 将内容写入文件。 # 其中,filename为存储的文件路径,content为要写入的内容 def save2file(filename, content): with open(r''+filename, 'w', encoding='utf-8') as f: f.write(content) # 创建卷章目录文件夹。 # 其中,path为要创建的路径 def mkdir(path): folder = os.path.exists(path) if(not folder): os.makedirs(path) else: print('路径“' + path + '”已存在') # 主函数 def main(savePath, bookId, csrfToken): volume = getPageAndChapUrl(bookId, csrfToken) if (volume != None): getPage(savePath, volume, bookId) else: print('无法爬取该小说!') print("小说爬取完毕!") # 调用主函数 savePath = '/Volumes/BOOTCAMP/书籍爬取' #文件保存路径 bookId = '1115277' #小说id csrfToken = 'WhR5PHkjqIavSHyA2kQwgTexiH3G09lyEBP8fZLr' #csrfToken main(savePath, bookId, csrfToken)
(2)程序运行后控制台信息如下,可以看到首先会获取小说的目录信息并进行解析,然后开始依次下载各章节页面。
(3)查看下载目录,可以看到各章节内容均以 txt 文档的形式保存在各卷文件夹下了。
全部评论(0)