Python爬蟲(一):用 Requests + BeautifulSoup 爬取網站上的信息

Python爬蟲(一)

要爬取網站的信息,
首先,要了解服務器與本地的交換機制;
其次,我們還要了解解析真實網頁的辦法。

一、服務器與本地的交換機制

我們知道,網頁在瀏覽器中顯示內容,都是網頁向所部署的服務器進行請求,也就是 Request,然後服務器進行相應,也就是 Response,這也就是 HTTP 協議的大致方式。Request方法
九成以上的網頁都只使用 GETPOST 方法,在瀏覽器中 F12 打開開發者工具,網頁的所有信息都能查看到。

二、具體實現

首先導入庫 BeautifulSouprequests,然後進行 get 請求:

from bs4 import BeautifulSoup
import requests

url = "https://blog.csdn.net/qq_42650988"
wb_data = requests.get(url)
soup = BeautifulSoup(wb_data.text, "lxml")
print(soup)

這裏請求的就是我的博客地址,看打印出來的 soup
打印信息
可以看到其實就是網頁源代碼。
然後找到標題,右鍵點擊 檢查,會顯示出代碼位置:
標題
然後在代碼上右鍵選擇 Copy 中的 Copy Selector,在python中鍵入:

titles = soup.select('#mainBox > main > div.article-list > div:nth-child(1) > h4 > a')
print(titles)

查看打印信息:
在這裏插入圖片描述
對於 select 函數,若想要找設定圖片寬度爲某一特定值,比如100px,可以這樣寫:

imgs = soup.select('img[width="100px"]')

這樣可以找到網頁中特定的元素。
另外,看這行代碼:

titles = soup.select('# mainBox > main > div.article-list > div:nth-child(1) > h4 > a')

只能找到 div-article-list 下的第一個 div 標籤中的標題,但是我們想獲得該網頁下的所有標題,可以將標籤的屬性 nth-child(n) 去掉,就表明是獲得 div-article-list 下的所有 div 標籤,代碼如下:

titles = soup.select('#mainBox > main > div.article-list > div > h4 > a')

這時我們用 object.get_text() 方法獲得標籤內的元素,打印內容如下:

[{'title': 'JavaScript簡單語法'}, {'title': 'HTML學習(八):CSS樣式設置小技巧'}, {'title': 'HTML學習(七):盒模型代碼簡寫以及單位和值'}, {'title': 'HTML學習(六):CSS佈局模型'}, {'title': 'HTML學習(五):CSS盒模型'}, {'title': 'JavaWeb世界(十三):過濾器和監聽器'}, {'title': 'JavaWeb世界(十二):驗證碼的實現和防止表單重複提交'}, {'title': 'JavaWeb世界(十一):購物車的設計與實現'}, {'title': 'JavaWeb世界(十):簡單的登錄與註銷'}, {'title': 'Execl的導入與導出'}, {'title': 'JavaWeb世界(九):文件上傳與下載'}, {'title': 'JavaWeb世界(八):MVC思想與合併Servlet'}, {'title': 'Windows10新版本啓動虛擬機報錯問題'}, {'title': 'Linux點點滴滴(三):SCP免密傳輸以及在Linux上設置和Windows的共享文件夾'}, {'title': '邁入操作系統的大門(二):環境配置'}, {'title': '邁入操作系統的大門(一)'}, {'title': 'Linux點點滴滴(二):在Linux上安裝GNU工具鏈並進行編譯'}, {'title': 'Linux點點滴滴(一):SSH協議'}, {'title': 'CLion中無法用相對路徑讀入文件'}, {'title': 'JavaWeb世界(七):EL與JSTL'}, {'title': 'Jython使用'}, {'title': 'JavaWeb世界(六):動態網頁和JSP'}, {'title': 'JavaWeb世界(五):Web之間組件共享、Servlet三大作用域對象'}, {'title': 'JavaWeb世界(四):Servlet映射與線程、Cookie、Session'}, {'title': 'JavaWeb世界(三):Servlet的請求和相應'}, {'title': 'JavaWeb世界(二):Servlet'}, {'title': 'JavaWeb世界(一):JavaWeb簡介、Web項目部署和HTTP協議'}, {'title': 'Windows環境下更新pip和安裝ipython'}, {'title': 'Java之路(十二):模擬Hibernate'}, {'title': 'Java之路(十一):DAO優化'}, {'title': 'Java之路(十):JDBC及DAO'}, {'title': 'Android中ListView對Item設置點擊事件無效的情況'}, {'title': 'ContentProvider'}, {'title': 'Android studio 引用 butterKnife包錯誤解決'}, {'title': '接口回調'}, {'title': 'Markdown常用特殊符號'}, {'title': '數據庫學習(MySQL)一:表'}, {'title': 'Java之路(九):JavaBean規範、內省機制及註解'}, {'title': 'Java之路(八):枚舉類和lambda表達式'}, {'title': 'Java中用String創建對象詳解'}]

我們再來觀察網頁的佈局,看到在 a 標籤下還有一個 span 標籤:
在這裏插入圖片描述
span 標籤便是標記該博客是否爲原創還是轉載等,真正的 a 標籤的內容變裸露在外面(其實也可以再用一個text之類的標籤再包一下),當我們獲得 a 標籤的內容時,其實是獲得了 span 標籤和 a 標籤的兩個內容:

for title in titles:
    data = {
        'title': title.get_text()
    }
	print(data)

這樣獲得的內容爲:
在這裏插入圖片描述
是這個樣子的,兩個標籤的元素在同一個集合中,可以使用函數 object.stripped_strings 將兩個元素分開,用 list 存放起來,是這樣的:在這裏插入圖片描述

for title in titles:
    data = {
        'title': list(title.stripped_strings)
    }
	print(data)

這就將其分開了。

這樣做的好處是:因爲網頁中的標籤有很多是一對多的關係,就是一個標籤下有很多其他標籤,比如一個標題下有兩張圖片,並且只要獲取其中的第二張圖片,如果僅僅是使用 get_text() 方法的話,標題有十個,圖片有二十張,這樣很難將第二張圖片提取出來,因此要把每一個標題下的內容單獨放在列表中,便於後面的操作。
最終,我們將網頁的標題、內容和閱讀數都提取了出來。
很簡單的一段代碼:

from bs4 import BeautifulSoup
import requests

url = "https://blog.csdn.net/qq_42650988"
wb_data = requests.get(url)
soup = BeautifulSoup(wb_data.text, "lxml")
# print(soup)

titles = soup.select('#mainBox > main > div.article-list > div > h4 > a')
contents = soup.select('#mainBox > main > div.article-list > div > p > a')
readers = soup.select(
    '#mainBox > main > div.article-list > div > div.info-box.d-flex.align-content-center > p:nth-child(3) > span > span')

for title, content, reader in zip(titles, contents, readers):
    data = {
        'title': list(title.stripped_strings),
        'content': content.get_text(),
        'readers': reader.get_text()
    }
    print(data)

打印結果:
res

三、網站加密和登錄狀態情況

有些網站會進行加密,使用了反爬取技術,防止不法爬取網站信息。例如會將圖片地址通過各種手段加密,將真正的地址放到 js 代碼塊中,用一個其他字段進行映射:
在這裏插入圖片描述
這種情況下,我們便很難提取出網站圖片的真實地址。但是後面會介紹一種比較簡單可行的方法來解決這一問題。
還有一種情況,當想獲取需要登錄才能得到的內容時,比如每個人收藏的內容,只有當登錄的時候才能看到,我們觀察:
Cookie
每次登錄時,在請求頭當中,都會有代表身份識別的一個參數 Cookie,我們只要將請求頭中加入 Cookie 參數,就可以向服務器提交僞造登錄的狀態。
類似下面的代碼:

headers {
	'user-agent': '',
	'cookie': ''
}
...
wb_data = requests.get(url, headers=headers)
...

這樣就可以獲取到需要登錄狀態的信息了。

四、爬取網頁中的多頁信息

一般情況下,網頁中的每一頁都是一個鏈接,當然我們不可能爲每一頁都寫一個函數去爬取,因此需要一些手段來自動獲取。
然而有些網站的地址是以 ?page= 結尾,來定位到某一頁,但是有的網站不是這樣,但是都是大同小異,需要仔細觀察網頁的地址。
page
可以這樣寫來列出所有頁的地址:

str = ['https://blog.csdn.net/qq_42650988/article/list/{}'.format(str(i)) for i in range(1, 10)]

打印出來就是這樣的:
在這裏插入圖片描述
然後再循環依次獲取就可以了。

五、完整代碼

from bs4 import BeautifulSoup
import requests
import time

url = "https://blog.csdn.net/qq_42650988"

headers = {
    'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.108 Safari/537.36',
    'cookie': 'uuid_tt_dd=10_23071848620-1563538289774-711233; dc_session_id=10_1563538289774.643389; UN=qq_42650988; Hm_ct_6bcd52f51e9b3dce32bec4a3997715ac=6525*1*10_23071848620-1563538289774-711233!1788*1*PC_VC!5744*1*qq_42650988; __yadk_uid=3EiQOte5NfVuDkkLCuvrXey9Aa7e9hqu; Hm_ct_e5ef47b9f471504959267fd614d579cd=5744*1*qq_42650988!6525*1*10_23071848620-1563538289774-711233; Hm_lvt_e5ef47b9f471504959267fd614d579cd=1576677401,1576828024; UserName=qq_42650988; UserInfo=425c9226bd674dadb99d2240fbf1cecd; UserToken=425c9226bd674dadb99d2240fbf1cecd; UserNick=Cherry%7E%7E; AU=2D2; BT=1578382768214; p_uid=U000000; __gads=ID=79953c24b4b04e08:T=1580019431:S=ALNI_Mb8neuiTfgjF0qD2mFW2dVXfhJjww; TY_SESSION_ID=cba3b681-6ee2-4cd0-9c56-4d6a6c02dd06; Hm_lvt_6bcd52f51e9b3dce32bec4a3997715ac=1581575929,1581577412,1581577643,1581578136; Hm_lpvt_6bcd52f51e9b3dce32bec4a3997715ac=1581597882; announcement=%257B%2522isLogin%2522%253Atrue%252C%2522announcementUrl%2522%253A%2522https%253A%252F%252Fblog.csdn.net%252Fblogdevteam%252Farticle%252Fdetails%252F103603408%2522%252C%2522announcementCount%2522%253A0%252C%2522announcementExpire%2522%253A3600000%257D; dc_tos=q5n4qp'
}

def get_attractions(url, data=None):
    wb_data = requests.get(url)
    time.sleep(2)
    soup = BeautifulSoup(wb_data.text, "lxml")
    titles = soup.select('#mainBox > main > div.article-list > div > h4 > a')
    contents = soup.select('#mainBox > main > div.article-list > div > p > a')
    readers = soup.select(
        '#mainBox > main > div.article-list > div > div.info-box.d-flex.align-content-center > p:nth-child(3) > span > span')
    for title, content, reader in zip(titles, contents, readers):
        data = {
            'title': list(title.stripped_strings),
            'content': content.get_text(),
            'readers': reader.get_text()
        }
        print(data)

url = 'https://me.csdn.net/follow/qq_42650988'

def get_fav(url, data=None):
    wb_data = requests.get(url, headers=headers)
    soup = BeautifulSoup(wb_data.text, 'lxml')
    fans = soup.select(
        'body > div.me_wrap.clearfix > div.me_wrap_l.my_tab_page.clearfix > div.me_chanel_det.clearfix > div.chanel_det_list.clearfix > ul > li > div > p > a')
    for fan in fans:
        data = {
            'fan': list(fan.stripped_strings)
        }
        print(data)


urls = ['https://blog.csdn.net/qq_42650988/article/list/{}'.format(str(i)) for i in range(1, 3)]
for single_url in urls:
    get_attractions(single_url)

六、利用手機端的瀏覽來爬取被加密的內容

有的時候在電腦上瀏覽的網頁會通過各種手段加密,但是在手機上,可能有些瀏覽器無法完整的加載所有 js 部分,因此整體結構會相對比較簡單。我們打開監視器,點擊左上角的手機圖標,調整手機型號,然後刷新一下,就可以展現出在手機端的頁面了:
在這裏插入圖片描述
因此我們需要獲取到請求頭中的信息來僞造是用手機訪問。

headers = {
    'User-Agent': 'Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/11.0 Mobile/15A372 Safari/604.1'
}

七、獲取動態數據

有時候我們瀏覽一個網頁的時候,所有內容不是一次性加載完畢,而是點擊類似於 加載更多 後者鼠標滾輪往下滑動的時候會自動加載。這些都是通過 JS 代碼來實現的。這也就是所謂數據的 異步加載。最典型的就是我們的QQ空間,往下拉到底的時候會繼續加載更早的內容。

其實很簡單,跟同事獲取多頁的數據是一樣的,要觀察獲取更多數據的請求的特點,就可以用代碼構造出響應的請求去獲取數據。

因爲剛開始學習,時間有限,又是比較基礎的東西,具體的示例將來有機會再寫。

發佈了54 篇原創文章 · 獲贊 19 · 訪問量 2萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章