BeautifulSoup爬取網頁分頁


在前面我們介紹了如何通過某個頁面爬取與之關聯的外部網頁,當時介紹的是使用廣度優先搜索的方式爬取。

在本節,我們將介紹另一種爬取外部鏈接的方式,即深度優先搜索,爬取網頁的分頁。

由於本人喜歡古詩詞,今天爬取的網頁的內容就是古詩詞,爬取的鏈接為:https://so.gushiwen.org/shiwen/。

如下圖所示:

在同一個網頁,內容是通過分頁的形式進行展示,今天介紹如何爬取分頁。

一、思路分析

我們知道,對於網頁的分頁內容訪問,我們通常可以通過點擊“上一頁”或者“下一頁”按鈕訪問關聯的頁面。

因此,在爬取分頁內容的時候,我們可以采用這一思路,進行深度遞歸,即可訪問所有的分頁內容。

當然,事實上,由於軟件工程師在編碼過程中會對不同分頁的url做一定的映射,因此除了以上思路,我們可以選擇另外一種解決辦法就是分析這種映射關系是什么。

如果能確定映射關系,那么就不需要進行深度遞歸,畢竟操作系統對於編程語言的棧的深度是有限制的,比如python最多遞歸1000次,超過1000次將報棧溢出的錯誤。

在接下來的示例演示中將對兩種方法進行介紹。

二、示例演示

1.深度遞歸訪問分頁內容

正如之前一直強調的,在使用網絡爬蟲進行網頁爬取的時候,第一步總是先打開目標網頁,然后在開發者模式下分析網頁的特點,然后根據分析的結果進行爬取。

打開目標網頁的結構如下圖所示:

在網頁中搜索“下一頁”按鈕所在的標簽位置,我們可以看到如上的結果。並且通過認真觀察,我們能夠看見“下一頁”所在的標簽是一個超鏈接,因此我們可以猜測,這個鏈接就是用來從當前頁跳轉到下一頁。

為了驗證我們的猜想是否正確,我們可以直接點開“下一頁”按鈕的跳轉頁面是否與我們剛才觀察的url一致。

打開“下一頁”的頁面,我們可以看到url如下:

顯然,兩個頁面是存在聯系的。瀏覽器中的url和超鏈接的url除了隔了域名https://so.gushiwen.org/不一樣之外,后半部分都是一致的。

因此證實了我們的猜想是正確的,因此我們只需要獲取頁面中“下一頁”中超鏈接的url,再拼接上域名,就能得到下一頁頁面的url。通過遞歸操作,我們能夠爬取所有的頁面。

這里定義一個函數進行實現,使用一個隊列存儲每次獲取的url:

def getNextUrl(url,que):
    '''

    :return:
    '''
    if url==None or len(url)==0:
        return
    try:
        html = requests.get(url)
        html.encoding = None
        new_url = bs.findAll("a", {"class": "amore"})[0].attrs['href']
        new_url= domain_prefix+new_url
        que.put(new_url)
        getNextUrl(new_url,que)
    except Exception as e:
        print("get attr error!")

在找到不同分頁頁面間的關聯關系之后,接下來就是找到我們的目標內容:古詩詞所在的位置。通過我們前面介紹的知識,我們很容易找到我們的目標內容:

利用我們上一章的講過的標簽定位方法,很容易就可以獲取到目標內容。

為了方便查看,我們把每首詩的內容使用文件進行存儲,代碼實現如下:

def get_poetry(bs):
    '''
    獲取每首詩的內容

    :param url:
    :return:
    '''

    # 將所有詩詞內容寫入文本
    poetry_set = bs.findAll("div", {"class": "sons"})
    for poetry in poetry_set:
        titles = poetry.findAll('b')
        if len(titles)==0:
            break

        title=titles[0].text.split('/')[-1].strip()
        res=''+title
        with open('./book/'+title+".txt", 'w') as fo:
            info = poetry.findAll('p', {'class': 'source'})
            for i in info:
                fo.write(i.get_text())
                res+=','+i.get_text()

            content = poetry.findAll('div', {'class': 'contson'})
            for i in content:
                fo.write(i.get_text())
                res += ','+i.get_text()

為了提高執行效率,我們可以把頁面內容提取和url訪問分開,使用多線程進行並行操作。同時,要考慮到訪問過程中會存在訪問出錯的情況,因此需要捕獲異常。

最終得到的完整代碼如下:

# 請求庫
import requests
# 解析庫
from bs4 import BeautifulSoup
from queue import Queue
import threading
import os
import re

domain_prefix=r"https://so.gushiwen.org"

def get_poetry(bs):
    '''
    獲取每首詩的內容

    :param url:
    :return:
    '''

    # 將所有詩詞內容寫入文本
    poetry_set = bs.findAll("div", {"class": "sons"})
    for poetry in poetry_set:
        titles = poetry.findAll('b')
        if len(titles)==0:
            break

        title=titles[0].text.split('/')[-1].strip()
        res=''+title
        with open('./book/'+title+".txt", 'w') as fo:
            info = poetry.findAll('p', {'class': 'source'})
            for i in info:
                fo.write(i.get_text())
                res+=','+i.get_text()

            content = poetry.findAll('div', {'class': 'contson'})
            for i in content:
                fo.write(i.get_text())
                res += ','+i.get_text()

def getNextUrl(url,que):
    '''

    :return:
    '''
    if url==None or len(url)==0:
        return

    try:
        html = requests.get(url)
        html.encoding = None
        bs = BeautifulSoup(html.text, 'html.parser')
        #啟動一個子線程進行內容處理
        t = threading.Thread(target=get_poetry, args=(bs,))
        t.start()
        t.join()

        new_url = bs.findAll("a", {"class": "amore"})[0].attrs['href']
        new_url= domain_prefix+new_url
        que.put(new_url)
        getNextUrl(new_url,que)
    except Exception as e:
        print("get attr error!")

爬取的網頁鏈接

url=r"https://so.gushiwen.org/shiwen/"
html=requests.get(url)
html.encoding=None
bs=BeautifulSoup(html.text,'html.parser')

#爬取所有的網頁鏈接
urls=Queue()
getNextUrl(url,urls)

最終得到的結果如下:

2.研究映射關系快速訪問

通過遞歸的方式獲取所有的url思路很清晰,也很簡單,但是效率不高。因此,為了提高效率,我們可以比較分析不同的特點,找出規律。

摘取部分url展示如下:

通過細細比較,我們可以很容易發現,這些url除了最后的1位或者兩位數字不同之外,其他的都是相同的,並且這些數字存在一個以1為步長的線性增長關系。

因此我們完全可以通過一個循環來生成所有的url,代碼實現如下:

#所有的網頁鏈接
urls=Queue()
for i in range(1,101,1):
    tmp=url.replace("1.aspx",str(i)+".aspx")
    print(tmp)
    urls.put(tmp)

在得到所有的URL之后,接下來我們只需要抓取每個頁面的url的HTML頁面,然后獲取內容皆可。除了獲取url的方式不同之外,其他抓取的方式與深度遞歸方式差不多,如下所示:

def get_poetry(que):
    '''
    獲取每首詩的內容

    :param url:
    :return:
    '''
    while not que.empty():
        url=que.get()

        try:
            html = requests.get(url)
            html.encoding = None
            bs = BeautifulSoup(html.text, 'html.parser')
            # 將所有詩詞內容寫入文本
            poetry_set = bs.findAll("div", {"class": "sons"})
            for poetry in poetry_set:
                titles = poetry.findAll('b')
                if len(titles)==0:
                    break

                title=titles[0].text.split('/')[-1].strip()
                print(title)
                res=''+title
                with open('./book/'+title+".txt", 'w') as fo:
                    info = poetry.findAll('p', {'class': 'source'})
                    for i in info:
                        fo.write(i.get_text())
                        res+=','+i.get_text()

                    content = poetry.findAll('div', {'class': 'contson'})
                    for i in content:
                        fo.write(i.get_text())
                        res += ','+i.get_text()
                    
        except Exception as e:
            print(url)
            print("get attr error!")

因為抓取的頁面之間不存在先后關系,因此可以並行執行。這里采用多線程的方式來提高抓取的效率。

得到最終的代碼如下:

# 請求庫
import requests
# 解析庫
from bs4 import BeautifulSoup
from queue import Queue
import threading
import pyttsx3
import os
import re

domain_prefix=r"https://so.gushiwen.org"

def get_poetry(que):
    '''
    獲取每首詩的內容

    :param url:
    :return:
    '''
    while not que.empty():
        url=que.get()

        try:
            html = requests.get(url)
            html.encoding = None
            bs = BeautifulSoup(html.text, 'html.parser')
            # 將所有詩詞內容寫入文本
            poetry_set = bs.findAll("div", {"class": "sons"})
            for poetry in poetry_set:
                titles = poetry.findAll('b')
                if len(titles)==0:
                    break

                title=titles[0].text.split('/')[-1].strip()
                print(title)
                res=''+title
                with open('./book/'+title+".txt", 'w') as fo:
                    info = poetry.findAll('p', {'class': 'source'})
                    for i in info:
                        fo.write(i.get_text())
                        res+=','+i.get_text()

                    content = poetry.findAll('div', {'class': 'contson'})
                    for i in content:
                        fo.write(i.get_text())
                        res += ','+i.get_text()

        except Exception as e:
            print(url)
            print("get attr error!")

# 爬取的網頁鏈接
url=r"https://so.gushiwen.org/shiwen/default_2A515ea88d1858A1.aspx"

#爬取所有的網頁鏈接
urls=Queue()
# urls.put(url)
for i in range(1,101,1):
    tmp=url.replace("1.aspx",str(i)+".aspx")
    print(tmp)
    urls.put(tmp)

# 定義多線程
for i in range(10):
    t = threading.Thread(target=get_poetry, args=(urls,))
    t.start()
    t.join()

通過執行對比分析,我們很容易就可以發現第二種方法比第一種方法不僅代碼更為簡潔,效率也更高。

當然,抓取內容只是整個數據分析的基本操作,為了對數據進行更深入的理解,我們需要借助一些數據分析的工具對數據進行處理和分析。

由於涉及的內容較多,難度也更大,將在后續的篇章逐漸給大家進行介紹。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM