BS4 爬取小說

      在〈BS4 爬取小說〉中尚無留言

美麗的湯

BeautifulSoup 簡稱 bs4,可以把 requests 取得的 html 進行樹狀節點化,然進行分析或搜尋。

早期曾在 xml解析 一篇中介紹 xml.etree.ElementTree 對 xml 進行分析。那麼 html 也是屬於 xml 的一種,所以 html 是否也可以使用 ElementTree進行分析呢? 答案是不行的,因為 html 內含 javascript,若使用 ElementTree 則會出現例外處理而閃退。

在正規表式示裏, 符號多且不好背也不好理解, 讓人非常煩雜, 所以很多人都不喜歡用正規表示式。此時 bs4 就派上用場了。

安裝套件

請先使用如下指令安裝所需之套件。

若沒有安裝lxml的話,bs4 會使用標準庫中的HTML解析器。不過 lxml 解析器更強大,速度更快,推薦安裝。

pip install beautifulsoup4 lxml

爬取小說–天降巨富

先看下面代碼,爬取天降巨富這部無聊的小說。本例因有簡体轉繁体的功能,所以請先下載 langconv.py 及 zh_wiki.py 二個檔案,置於專案根目錄之下。

完整代碼如下

import os.path
import shutil
import requests
from bs4 import BeautifulSoup
from langconv import Converter
url="https://funs.me"
path="e:/天降巨富"
if os.path.exists(path):
    shutil.rmtree(path)
os.mkdir(path)
page = requests.get(f"{url}/book/1828.html")
page.encoding="utf-8"
soup=BeautifulSoup(page.text, "html.parser")
nodes=soup.find_all("a")
data={}
for node in nodes:
    if "第" in node.text:
        print (node.text, node.get("href"))
        data[node.text]=f"{url}{node.get('href')}"
for key in data:
    print(F"正在爬取 {key}........")
    page=requests.get(data[key])
    page.encoding="utf-8"
    soup=BeautifulSoup(page.text, "html.parser")
    content=soup.find(id="ChSize")
    ps=content.find_all("p")
    filename=os.path.join(path, f'{key.replace("?","")}.txt')
    with open(filename, "w", encoding="utf-8") as file:
        for p in ps:
            zh=Converter('zh-hant').convert(p.text)
            file.write(zh)
            file.write("\n")

使用方式

import requests, re
from bs4 import BeautifulSoup
page = requests.get("https://funs.me/book/1828.html")
page.encoding="utf-8"
soup=BeautifulSoup(page.text, "html.parser")
print(soup.prettify())
結果: 
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
 <title>
 天降巨富最新章节列表,全文阅读,UU小说网无弹窗广告
 </title>
 <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
 <meta content="小说天降巨富最新章节列表,全文阅读" name="keywords"/>
 <meta content="感谢您支持陆原居的小说天降巨富,UU小说网提供天降巨富最新章节连载和在线阅读,更新速度最快且无弹窗广告,7*24小时不间断更新天降巨富,请收藏以方便今后阅读。" name="description"/>
.................
 <div id="intro">
 <p>
 作为一个超级富二代装穷是一种什么体验?别拦着我,没有人比我更有资格回答这个问题!
 </p>
 <p>
 本站提示:各位书友要是觉得《天降巨富》还不错的话请不要忘记向您QQ群和微博里的朋友推荐哦!
 </p>
 </div>
 </div>
.........................

上述是使用requests取得網頁 page 後,再將 page.text 傳入 bs4 內,最後使用 prettify() 將整個結構依內縮格式完全印出來。

ps : prettify [ˋprɪtɪ͵faɪ] : 美化

本地端檔案

requests 的 get、 post 都必需是遠端被在伺服器的檔案。對於本地端無 web server 的網頁檔,則無法開啟。 但 bs4 確可以輕易的打開,如下所示。

soup = BeautifulSoup(open(index.htm))
print(soup.prettify())

請注意, 要開啟的檔案, 必需儲存為 ANSI 格式,不可以使用 utf-8。所以請先用記事本重新檢查看看

遍訪整個樹狀結構

bs4會將html的每個標籤轉換成節點, 並以樹狀結構存放在記憶体裏

soup.head : 傳回head節點
soup.title : 傳回title節點
soup.body : 傳回body節點
soup.a : 傳回第一個標籤為<a>的節點
soup.h1 : 傳回第出第一個標籤為<h1>的節點

soup.body.contents : 將 body 內的子節點以list傳回, 可以使用len()查得裏面有幾個節點數
soup.body.children : 將 body 內的子節點以list產生器傳回, 需使用 foreach 列印出來

strings

具有多個節點的物件, 就可以使用strings將所有的子節點的內容(text)印出, 比如soup.body.strings. 此法亦需使用foreach印出

ls=soup.body.strings
for l in ls:
    print(l)

搜尋整顆樹狀結構

soup.a : 列印第一個<a>標籤, 若加string, 則會去除標籤(僅適用於單一節點)

node = soup.find(‘a’, href=’/ls/84_84080/’) : 搜尋第一筆 節點為 <a>, 屬性為 href=’/ls/84_84080/’
node.get(“href”) : 取得節點的 href 屬性
ls=soup.find_all(‘a’, href=’/ls/84_84080/’, recursive=True) : 同上, 但搜尋所有的文件, 並將結果以List傳回
ls=soup.find_all([‘a’, ‘b’], limit=2) : 搜尋多個條件, 並限制傳回的筆數
ls=soup.find_all(“b”,string=”小影后她又奶又萌”) : 搜尋特定節點中,text 為 “小影后她又奶又萌”的節點,結果以list傳回。

底下的代碼,則是利用上述的方法,將天降巨富小說中的文章超連節印出

import requests, re
from bs4 import BeautifulSoup
page = requests.get("http://www.uuxs.tw/ls/22_22102/")
page.encoding="utf-8"
soup=BeautifulSoup(page.text, "html.parser")
dds=soup.find_all('dd')
for node in dds:
    link=node.find('a')
    print(node, link.get('href'), link.get('title'))
結果 :
<dd><a href="22145254.html" title="第五百五十章 打开后备厢看看">第五百五十章 打开后备厢看看</a></dd> 22145254.html 第五百五十章 打开后备厢看看
<dd><a href="22274451.html" title="第五百五十一章 赵思思何去何从">第五百五十一章 赵思思何去何从</a></dd> 22274451.html 第五百五十一章 赵思思何去何从
<dd><a href="22416022.html" title="第五百五十二章 外婆舅舅这一家">第五百五十二章 外婆舅舅这一家</a></dd> 22416022.html 第五百五十二章 外婆舅舅这一家
<dd><a href="23488023.html" title="第五百五十三章 把亲生母亲接来">第五百五十三章 把亲生母亲接来</a></dd> 23488023.html 第五百五十三章 把亲生母亲接来
<dd><a href="23509205.html" title="第五百五十四章 去别墅接回曹凤">第五百五十四章 去别墅接回曹凤</a></dd> 23509205.html 第五百五十四章 去别墅接回曹凤

正規表示法搜尋

條件內可以擺放正規表示法, 首先必需先import re

import re
s='123456789'
p=re.compile('[a,5].*')
a=p.findall(s)
print(a)

“.”  : 任何字元
“*” : 至少匹配 0 次
“^” : 整行的開頭

‘^1’ :  1
‘^1.*’ : 123456789

“$” : 整行的結尾

‘[]’ : 匹配到[]內的任一字元

‘[a,5]’ : 5
‘[a,5].*’ : 56789

‘\d’ : 任何數字

‘\D’ : 任何非數字

ls=soup.find_all('a', href=re.compile(("^https:.*")))

節點種類

bs4將html轉成樹狀結構後, 每個節點的種類有四種
Tag, NavigableString, BeautifulSoup, Comment

Tag

就是html中的標籤, 如<head>, <body>, <a href=”xxx”>, 標籤加上裏面的內容,  就形成了Tag

print(soup.title)
print(soup.a)
結果 :
<title>天降巨富最新章节列表,全文阅读,UU小说网无弹窗广告</title>
<a href="#" onclick="this.style.behavior='url(#default#homepage)';this.setHomePage('http://www.uuxs.tw/');">将UU小说网设为首页</a>

上面<a>標籤有上百個, 但使用 soup.a只會抓到第一個

每個標籤都有其屬性, 使用下列方式可取得屬性值
soup.a.name : 傳回節點的標籤名稱, 即 a
soup.a.attrs : 傳回節點中所有屬性的list
soup.a[‘class’] : 傳回屬性為 ‘class’ 的值
soup.a[‘class’]=”newClass’ : 設定新的class名
del soup.a[‘class’] : 刪除屬性名

NavigableString

取得每個標籤的內容, 可以使用
soup.a.string

BeautifulSoup

最上面的根節點, 即為整個文件檔的內容

Comment

comment為註解的意思. 比如
<a class=”pokemon” href=”http://abc.com.tw”><!– Super –> </a>
使用soup.a.string, 則會取出去除註解後的字串 “Super”

若不想把註解印出, 需使用如下方式

if type(soup.a.string)!=bs4.element.Comment:
    print(soup.a.string)

補充說明

開啟文字檔方式, 可以使用open(‘檔名’,’模式’, -1), -1表示緩衝大小採預設值, 常用模式如下
r : 只用於讀取
w : 只用於寫入, 會刪除舊檔案
w+ : 用於讀及寫
a : 附加檔尾寫入

file=open(r'd:\pytest.txt','a',-1)
file.write("第一行\n")
file.write("第二行\n")
file.write("第三行\n")
file.close()

open()並不處理編碼, 字串是什麼碼, 它就寫入什麼碼. 所以如果想要統一使用utf-8, 就要使用codecs

遞迴列印所有節點的內容

下面的方式, 是練習上述的觀念, 使用自已的方式將每個節點的內容印出來

import requests
from bs4 import BeautifulSoup
def analysis(nodes):
    if len(nodes)==1:
        try:
            print("attrs: %s : " % (nodes[0].attrs), end="")
        except:
            print("error:", end="")
        print("%s, %s" % (nodes[0].name, nodes[0].string))
    else:
        for n in nodes:
            if n.name !=None:
                print("--------------------------%s------------------" % (n.name))
                analysis(n.contents)
page = requests.get("http://www.uuxs.tw/ls/22_22102/")
soup=BeautifulSoup(page.text, "html.parser")
analysis(soup.contents)

Yahoo 頭條新聞

Yahoo 新聞超連結都有 class=story-title , 所以我們只要找出網頁中所有符合此條件的標籤,就可以把頭條新聞的資訊抓出來了。

import requests
from bs4 import BeautifulSoup
page = requests.get('https://tw.yahoo.com/')
page.encoding="utf-8"
if page.status_code == requests.codes.ok:#確認是否下載成功
    soup = BeautifulSoup(page.text, 'html.parser')
    #ls = soup.find_all('a', class='story-title')
    ls = soup.find_all('a')
    for l in ls:
        print("標題:" + l.text)
        print("網址:" + l.get('href'))
else:
    print("下載失敗")

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *