返回 导航

Python

hangge.com

Python - 第三方HTTP库Requests使用详解5(BeautifulSoup、网页爬虫)

作者:hangge | 2022-06-30 09:11

六、使用 BeautifulSoup 解析 HTML 页面代码

1,基本介绍

简单来说,Beautiful Soup 就是 Python 的一个 HTMLXML 的解析库,使用它可以很方便地从网页中提取数据。
  • Beautiful Soup 提供一些简单的、Python 式的函数来处理导航、搜索、修改分析树等功能。它是一个工具箱,通过解析文档为用户提供需要抓取的数据,因为简单,所以不需要多少代码就可以写出一个完整的应用程序。
  • Beautiful Soup 自动将输入文档转换为 Unicode 编码,输出文档转换为 UTF-8 编码。你不需要考虑编码方式,除非文档没有指定一个编码方式,这时你仅仅需要说明一下原始编码方式就可以了。
  • Beautiful Soup 已成为和 lxmlhtml6lib 一样出色的 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 下的第 1a 标签中:

(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

(2)接着 F12 打开浏览器控制台,点击 Network,再点击 XHR,刷新页面可以得到网页的 csrfTokenbookId 也有):

(3)我们访问如下接口传入前面获取的 csrfTokenbookId 即可获取整个小说的目录。其中 vs 为所有的卷集合,每个卷下的 cs 为该卷所有的章节集合。

(4)每个章节的 cU 即为该章节页面的地址,我们拼接上固定的前缀即可访问该页面,接下来只要解析该页面内容,并保存到本地即可。

2,样例代码

(1)下面是爬虫完整代码,使用时只需要根据情况修改下 bookIdcsrfToken 以及下载文件保存地址即可:
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)

回到顶部