备份 CSDN 博客(上)
文章目錄
- 背景
- 思路解析
- 如何獲得每篇文章的 URL
- urllib.request
- HTML 的元素構成
- BeautifulSoup
- 根據標簽和屬性識別
- 根據標簽和內容識別
- 其他操作
- 代碼
- 參考資料
背景
因為 CSDN 的博客沒有批量導出功能,所以我就琢磨寫個腳本可以一鍵備份博客,最好是 markdown 格式。
搜了一波,極少有能拿來就用的,那就自己探索吧。
思路解析
思路很簡單:
囿于篇幅,這篇文章先解決第 1 個問題。
其實我不太懂 python 爬蟲和前端,算是現學現賣,說得不對的地方,請您指正。
如何獲得每篇文章的 URL
如圖所示,我的博客總共有 7 頁,第 2 頁的文章列表的地址是:https://blog.csdn.net/longintchar/article/list/2
第 3 頁的文章列表的地址是:
https://blog.csdn.net/longintchar/article/list/3
其中的規律很明顯,就是在 https://blog.csdn.net/longintchar/article/list/ 后面加上頁碼
當你把鼠標懸停在標題上,就能看到左下角會顯示這篇文章的 URL(我用的是谷歌瀏覽器)。所以,從文章列表頁面就可以解析出本頁面每一篇文章的 URL
以第 1 頁舉例,我們要做的事情有 2 個
urllib.request
urllib 是 Python 內建的 HTTP 庫,使用 urllib 可以只需要很簡單的步驟就能高效采集數據。
urllib 包含以下4個子模塊
其中 urllib.request 子模塊是最常用的,用來從網站獲取源代碼
一個簡單的示例:
# 引入 urllib.request import urllib.request # 打開 URL response = urllib.request.urlopen('http://www.zhihu.com') # 讀取內容 html = response.read() # 解碼 html = html.decode('utf-8') print(html)第 4 行:urlopen() 方法返回的是一個 http.client.HTTPResponse 對象(<class ‘http.client.HTTPResponse’>),需要通過 read() 方法做進一步的處理
第 6 行:調用 read() 方法,返回的數據類型為 bytes 類型
第 8 行:bytes 類型經過 decode() 解碼轉換成 string 類型
照貓畫虎,對于我們要下載的頁面,代碼是
import urllib.requesturl = 'https://blog.csdn.net/longintchar/article/list/1' res = urllib.request.urlopen(url) html = res.read().decode('utf-8') print(html)第 6 行打印結果,結果如下圖(截取了一小部分)
注意第 900 行,這里面就有我們要的文章 URL,如何把這些 URL 提取出來呢?
HTML 的元素構成
爬取網頁信息,可以理解成從 HTML 代碼中抽取我們需要的信息。HTML 由一系列的“元素”組成,這是從網上搜來的一張圖:
我們要做的,可以分成兩部分
- 精確定位元素
- 從元素中提取信息
比如下面這個元素
<p><a href='www.wenzi.com'>hello</a></p>一般要提取“hello”部分,或者鏈接 www.wenzi.com 部分
如何精確定位到某個元素呢?可以利用標簽名(比如上面的 “p”)和標簽屬性來識別。
例如:
<title>標題</title> <body><ul class='list1'><li>列表1第1項</li><li>列表1第2項</li></ul><p class='first'>文字1</p><p class='second'>文字2</p><ul class='list2'><li>列表2第1項</li><li>列表2第2項</li></ul> </body>- 如果要提取“標題”,只需要使用標簽名 title 來識別,因為只出現過一次 title 標簽
- 如果要提取“文字1”,不能只使用p標簽,因為“文字2”也對應了p標簽,所以要用p標簽且class屬性值是'first'來識別
- 如果“文字1”和“文字2”都要,就可以通過獲取所有p標簽提取內容
- 如果想提取列表1中的兩項,就不能靠獲取所有li標簽,因為列表2中也有li標簽。此時需要先識別其父節點,即先定位到<ul class='list1'>這個標簽上(通過ul標簽和class屬性值是list1定位)。在這個標簽里,所有li都是我們想要的
BeautifulSoup
BeautifulSoup 是一個 HTML/XML 的解析器,用來解析和提取 HTML/XML 數據,利用它不用編寫正則表達式也能方便地抓取網頁信息。
這里展示一下使用 BeautifulSoup 實現上述提取的代碼,以對這個庫的提取思路有一個大致的了解。
a = '''<title>標題</title> <body><ul class='list1'><li>列表1第1項</li><li>列表1第2項</li></ul><p class='first'>文字1</p><p class='second'>文字2</p><ul class='list2'><li>列表2第1項</li><li>列表2第2項</li></ul> </body>'''from bs4 import BeautifulSoup soup = BeautifulSoup(a, "html.parser") # 1. 如果要提取“標題”,只需要使用`title`標簽名來識別,因為只出現過一次`title`標簽 # 提取元素的內容:使用.text print(soup.title.text) # 2. 提取“文字1” # 注意,find方法,只能找到第一個 print(soup.find('p', attrs={'class':'first'}).text)# 3. 提取“文字1”和“文字2” print(soup.find_all('p')) # 再分別從中提取文字,這里略# 4. 提取列表1中的兩項 print(soup.find('ul', attrs={'class':'list1'}).find_all('li'))運行結果是:
標題 文字1 [<p class="first">文字1</p>, <p class="second">文字2</p>] [<li>列表1第1項</li>, <li>列表1第2項</li>]第 16 行,第二個參數指明解析器。BeautifulSoup 提供了三個解析器,它們各自的優缺點如下
- html.parser :內置不依賴擴展,容錯能力強,速度適中
- lxml:速度最快,容錯能力強,但是依賴 C 擴展
- html5hib:速度最慢,容錯能力最強,依賴擴展
第 30 行,當需要根據屬性來篩選的時候,可以用 attrs={屬性名:值}指定屬性的鍵值對。
根據標簽和屬性識別
我們再看幾個例子,請仔細看注釋和輸出結果。
a = ''' <p id='p1'>段落1</p> <p id='p2'>段落2</p> <p class='p3'>段落3</p> <p class='p3' id='pp'>段落4</p> '''from bs4 import BeautifulSoup soup = BeautifulSoup(a, "html.parser")# 第一種,直接將屬性名作為參數名,但是有些屬性不行,比如像"a-b"這樣的屬性 print(soup.find_all('p', id = 'p1') )# 一般情況 print(soup.find_all('p', class_='p3') )# class是保留字比較特殊,需要后面加一個_# 最通用的方法 print(soup.find_all('p', attrs={'class':'p3'}) )# 包含這個屬性就算,而不是僅有這個屬性 print(soup.find_all('p', attrs={'class':'p3','id':'pp'}) )# 使用多個屬性匹配 print(soup.find_all('p', attrs={'class':'p3','id':False}) )# 指定不能有某個屬性 print(soup.find_all('p', attrs={'id':['p1','p2']}) )# 屬性值是p1或p2 print(soup.find_all('p', attrs={'class':True})) # 含有class屬性即可 # 正則表達式匹配 import re print(soup.find_all('p', attrs={'id':re.compile('^p')})) # 使用正則表達式,id以p開頭 [<p id="p1">段落1</p>] [<p class="p3">段落3</p>, <p class="p3" id="pp">段落4</p>][<p class="p3">段落3</p>, <p class="p3" id="pp">段落4</p>] //第16行 [<p class="p3" id="pp">段落4</p>] // 第17行 [<p class="p3">段落3</p>] // 第18行 [<p id="p1">段落1</p>, <p id="p2">段落2</p>] // 第19行 [<p class="p3">段落3</p>, <p class="p3" id="pp">段落4</p>] // 第20行 [<p id="p1">段落1</p>, <p id="p2">段落2</p>, <p class="p3" id="pp">段落4</p>]需要說明的是:
- find 方法:只能找到第一個符合要求的標簽
- find_all 方法:找到所有符合要求的標簽,返回一個 list,如果只找到一個也是返回 list,可以用[0]提取
根據標簽和內容識別
繼續舉例子
a = ''' <p id='p1'>段落1</p> <p class='p3'>段落2</p> <p class='p3'>文章</p> <p></p> ''' from bs4 import BeautifulSoup soup = BeautifulSoup(a, "html.parser")print(soup.find_all('p', text='文章')) print(soup.find_all('p', text=['段落1','段落2']))# 正則表達式 import re print(soup.find_all('p', text=re.compile('段落')))# 傳入函數 def nothing(c):return c not in ['段落1','段落2','文章'] print(soup.find_all('p',text=nothing))def something(c):return c in ['段落1','段落2','文章'] print(soup.find_all('p',text=something))def nothing(c): return c is None print(soup.find_all('p',text=nothing))運行結果
[<p class="p3">文章</p>] [<p id="p1">段落1</p>, <p class="p3">段落2</p>] [<p id="p1">段落1</p>, <p class="p3">段落2</p>] // 第15行 [<p></p>] // 第20行 [<p id="p1">段落1</p>, <p class="p3">段落2</p>, <p class="p3">文章</p>] // 第24行 [<p></p>] // 第29行注意,代碼第 17 行和后面,舉例如何使用函數來過濾
20:把 nothing 這個函數作用在 text 上面,如果返回 True,則符合條件,其他類似。
find_all() 函數和 BeautifulSoup 的詳細說明,可以看官方文檔,地址是
https://beautifulsoup.readthedocs.io/zh_CN/v4.4.0/
其他操作
包括嵌套標簽的提取、獲取內容、獲取屬性值
a = ''' <body><h><a href='www.biaoti.com'>標題</a></h><p>段落1</p><p>段落2</p> </body> '''from bs4 import BeautifulSoup soup = BeautifulSoup(a, 'html.parser')# 提取內容for p in soup.find_all('p'):print(p.text)print(soup.h.text) # 多層嵌套也可以直接返回,即提取內層標簽 a 的內容 print(soup.h.a.text) # 也可以這樣 print(soup.a.text) # 也可以這樣print(soup.body.text) # 里面有多個內容時 # 提取屬性值,像字典一樣提取 print(soup.h.a['href']) print(soup.a['href']) # 也可以這樣 print(soup.h.a.get('href')) # 也可以這樣運行結果
段落1 段落2 標題 // 18-20 標題 標題標題 // 23 段落1 段落2www.biaoti.com // 26-28 www.biaoti.com www.biaoti.com代碼
終于鋪墊完了,可以講代碼了。
文件名:get_id.py
此模塊的功能是提取我所有文章的 URL
import urllib.request from bs4 import BeautifulSoupdef getid(x):url = 'https://blog.csdn.net/longintchar/article/list/' + str(x)res = urllib.request.urlopen(url) html = res.read().decode('utf-8')soup = BeautifulSoup(html,'html.parser')divs = soup.find_all('div', attrs={'class':'article-item-box csdn-tracking-statistics'})for div in divs:print(div.h4.a['href'])for i in range(1, 8):getid(i)因為我的博客有 7 頁,所以第 14 行 range 的參數是(1,8)
6:最開頭已經分析了,我的博客列表地址是 https://blog.csdn.net/longintchar/article/list/1 到 https://blog.csdn.net/longintchar/article/list/7
7-8:前文已經講了,利用 urllib.request 子模塊從網站獲取源代碼
9:利用 BeautifulSoup 解析 HTML 源碼
通過分析 HTML 的代碼,我發現我需要的鏈接在某些 div 標簽中,準確地說,是 div.h4.a 的 href 屬性對應的值。這個 div 標簽有特點,特點是其 class 屬性的值是 “article-item-box csdn-tracking-statistics”,靠這個屬性值就可以排除其他 div 標簽。
所以,就有這幾行代碼
divs = soup.find_all('div', attrs={'class':'article-item-box csdn-tracking-statistics'})for div in divs:print(div.h4.a['href'])運行 get_id.py,就可以輸出所有博文的鏈接。
如:
第一個問題已經搞定,下篇博文我們看看如何下載每篇文章,轉換成 markdown 格式。
參考資料
【1】BeautifulSoup全面總結
總結
以上是生活随笔為你收集整理的备份 CSDN 博客(上)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: c语言数组左下角便*,数据结构 - 数组
- 下一篇: 刷bios工具_微星主板怎么更新bios