Python爬蟲第十二課:複習及總結

一、爬蟲總複習

這張圖,它被用來描述瀏覽器的工作原理:

在這裏插入圖片描述

請求和響應可以說貫穿了我們後面的所有學習內容。

一開始,我們給爬蟲下了一個定義:利用程序在網上獲取對我們有用的數據。而獲取數據最關鍵的步驟正是“請求”和“響應”。

由此,我們將爬蟲大致分成了四個步驟:獲取數據(包含請求和響應兩個動作)、解析數據、提取數據、存儲數據。

在這裏插入圖片描述

首先我們學習了最簡單的請求方式:requests.get()

import requests
url = '(網址)'
response = requests.get(url)

在這裏插入圖片描述

工具

利用Network,我們能看到所有的請求,它是我們用來編寫爬蟲的利器。

Network能夠記錄瀏覽器的所有請求。而ALL(查看全部)和XHR(僅查看XHR)在我們的課程關卡中則是最常用的兩類。其他的諸如Doc(Document,第0個請求一般在這裏)、mg(僅查看圖片)、僅查看媒體文件)以及Other(其他)等偶爾也會用到

在這裏插入圖片描述

它們共同組成Elements中的內容,也就是你平時所見的各式各樣的網頁。

在爬蟲裏,我們最常用的是XHRDoc。我們能在Doc裏找到一個網頁的源代碼,而在網頁源代碼裏找不到的信息,通常你都能在XHR裏找到。有了它,我們不必刷新/跳轉網頁,即可加載新的內容。在今天,已經學過“同步/異步”概念的你,也可以說XHR幫我們實現了異步請求。

而如何找到我們想要的數據呢?

在這裏插入圖片描述

解析和提取

當我們爬取的是靜態頁面,也就是說數據藏匿於網頁源代碼時,爬蟲中至關重要的一環叫BeautifulSoup,它能提供一套完整的數據解析、數據提取解決方案。用法如下:

在這裏插入圖片描述

import requests
# 引用requests庫
from bs4 import BeautifulSoup
# 引用BeautifulSoup庫
headers={"user-agent":""}
res_movies = requests.get('https://movie.douban.com/chart',headers=headers)
# 獲取數據
bs_movies = BeautifulSoup(res_movies.text,'html.parser')
# 解析數據
list_movies= bs_movies.find_all('div',class_='pl2')
# 查找最小父級標籤

list_all = []
# 創建一個空列表,用於存儲信息

for movie in list_movies:
    tag_a = movie.find('a')
    # 提取第0個父級標籤中的<a>標籤
    name = tag_a.text.replace(' ', '').replace('\n', '')
    # 電影名,使用replace方法去掉多餘的空格及換行符
    url = tag_a['href']
    # 電影詳情頁的鏈接
    tag_p = movie.find('p', class_='pl')
    # 提取父級標籤中的<p>標籤
    information = tag_p.text.replace(' ', '').replace('\n', '')
    # 電影基本信息,使用replace方法去掉多餘的空格及換行符
    tag_div = movie.find('div', class_='star clearfix')
    # 提取父級標籤中的<div>標籤
    rating = tag_div.text.replace(' ', '').replace('\n', '')
    # 電影評分信息,使用replace方法去掉多餘的空格及換行符
    list_all.append([name,url,information,rating])
    # 將電影名、URL、電影基本信息和電影評分信息,封裝爲列表,用append方法添加進list_all

print(list_all)
# 打印

#以下是另外一種寫法

import requests
# 引用requests庫
from bs4 import BeautifulSoup
# 引用BeautifulSoup庫
headers={"user-agent":""}
res_movies = requests.get('https://movie.douban.com/chart',headers=headers)
# 獲取數據
bs_movies = BeautifulSoup(res_movies.text,'html.parser')
# 解析數據

tag_name = bs_movies.find_all('div', class_='pl2')
# 查找包含電影名和電影詳情URL的<a>標籤
tag_p = bs_movies.find_all('p',class_='pl')
# 查找包含電影基本信息的<p>標籤
tag_div = bs_movies.find_all('div', class_='star clearfix')
# 查找包含電影評分信息的<div>標籤
list_all = []
# 創建一個空列表,用於存儲信息
for x in range(len(tag_name)):
# 啓動一個循環,次數等於電影名的數量
    list_movie = [tag_name[x].find('a').text.replace(' ', '').replace('\n', ''),tag_name[x].find('a')['href'],tag_p[x].text.replace(' ', '').replace('\n', ''),tag_div[x].text.replace(' ', '').replace('\n', '')]
    # 提取信息,封裝爲列表。
    list_all.append(list_movie)
    # 將信息添加進list_all
print(list_all)
# 打印

需要額外強調一下,當response.text自動解碼出問題時,一定要使用response.encoding=''來對編碼進行修改。

我們來看當數據在XHR中時該如何處理。

XHR所傳輸的數據,最重要的一種是用json格式寫成的,把它打印出來顯示的樣式和字典是一樣的。但是,json的數據類型是“文本”,在Python語言當中,我們把它稱爲字符串。json格式和列表/字典之間是能相互轉化的。

解析json數據並不難,步驟如下:

在這裏插入圖片描述

有了json,就可以對數據進行提取,將其轉化爲字典/列表:

在這裏插入圖片描述

更厲害的請求

我們再來研究一下“請求”。最開始,requests.get()裏面只有一個參數,即url。

隨着學習的深入,我們知道請求可以有多個參數。

params,可以讓我們帶着參數來請求數據,例如我們想要跳轉到第幾頁、每次請求多少個數據等等。

訪問的時候帶上headers,即請求頭。它的作用是在訪問的時候告訴服務器:發出請求的瀏覽器是什麼?我從哪個頁面而來?

除了get請求之外,還存在着post請求方式。二者的區別在於:get是明文顯示參數,post是非明文顯示參數。

在post請求裏,又多了兩個參數:data和cookies。我們使用來data傳遞參數,其用法和params非常相像。

而cookies的作用是讓服務器“記住你”,比如一般當你登錄一個網站,你都會在登錄頁面看到一個可勾選的選項“記住我”。如果你點了勾選,服務器就會生成一個cookies和你的賬號綁定。接着,它把這個cookies告訴你的瀏覽器,讓瀏覽器把cookies存儲到你的本地電腦。當下一次,瀏覽器帶着cookies訪問博客,服務器會知道你是何人,你不需要再重複輸入賬號密碼,就能直接訪問。

import requests



url_1 = 'https://…'

headers = {'user-agent':''}

data = {}

# 定義url,headers和data



login_in = requests.post(url,headers=headers,data=data)

cookies = login_in.cookies

# 完成登錄,獲取cookies



url_2 = 'https://…'

params = {}

# 定義url和params



response = requests.get(url,headers=headers,params=params,cookies=cookies)

# 帶着cookies重新發起請求

存儲

成功獲取數據,接着解析數據,然後提取數據,最後都需要我們存儲數據。

存儲數據的方法有許多,其中最常見的是:csv和excel。
在這裏插入圖片描述
在這裏插入圖片描述

import csv

# 需要寫入的數據
score1 = ['math', 95]
score2 = ['english', 90]

# 打開文件,追加a, newline="",可以刪掉行與行之間的空格
file= open("score.csv", "a", newline="")

# 設定寫入模式
csv_write = csv.writer(file)

# 寫入具體內容
csv_write.writerow(score1)
csv_write.writerow(score2)

在這裏插入圖片描述

import csv

# 打開csv文件
file = open("score.csv")

# 讀取文件內容,構造csv.reader對象
reader = csv.reader(file)

# 打印reader中的內容
for item in reader:
    print(item)

在這裏插入圖片描述

import openpyxl
# 引用openpyxl
wb = openpyxl.Workbook()
# 利用openpyxl.Workbook()函數創建新的workbook(工作薄)對象,就是創建新的空的Excel文件。
sheet = wb.active
# wb.active就是獲取這個工作薄的活動表,通常就是第一個工作簿,也就是我們在上面的圖片中看到的sheet1。
sheet.title = 'kaikeba'
# 可以用.title給工作表重命名。現在第一個工作表的名稱就會由原來默認的“sheet1”改爲"kaikeba"。
sheet['A1'] = 'kaikeba'
# 向單個單元格寫入數據
score1 = ['math', 95]
sheet.append(score1)
# 寫入整行的數據,變量類型是一個列表
wb.save('score.xlsx')
# 保存修改的Excel
wb.close()
# 關閉Excel

在這裏插入圖片描述

import openpyxl
wb = openpyxl.load_workbook('score.xlsx')
# 打開的指定的工作簿
sheet = wb['kaikeba']
# 指定讀取的工作表的名稱
A1_value = sheet['A1'].value
print(A1_value)
# 獲取

更多爬蟲

掌握了這四步,世上已經少有你搞不定的爬蟲。但是,如果要爬取的數據特別特別多,以至於程序會被拖得很慢怎麼辦?用協程。

使用多協程的話,就能讓多個爬取任務用異步的方式交替執行。

在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述

from gevent import monkey

#從gevent庫裏導入monkey模塊

import gevent

import time

import requests

from gevent.queue import Queue

#從gevent庫裏導入queue模塊



monkey.patch_all()

#monkey.patch_all()能把程序變成協作式運行,就是可以幫助程序實現異步。

start = time.time()



url_list = ['https://www.baidu.com/',

'https://www.sina.com.cn/',

'http://www.sohu.com/',

'https://www.qq.com/',

'https://www.163.com/',

'http://www.iqiyi.com/',

'https://www.tmall.com/',

'http://www.ifeng.com/']



work = Queue()

#創建隊列對象,並賦值給work

for url in url_list:

#遍歷url_list

    work.put_nowait(url)

    #用put_nowait()函數可以把網址都放進隊列裏



def crawler():

    while not work.empty():

    #當隊列不是空的時候,就執行下面的程序

        url = work.get_nowait()

        #用get_nowait()函數可以把隊列裏的網址都取出

        r = requests.get(url)

        #用requests.get()函數抓取網址

        print(url,work.qsize(),r.status_code)

        #打印網址、隊列長度、抓取請求的狀態碼



tasks_list  = [ ]

#創建空的任務列表

for x in range(2):

#相當於創建了2個爬蟲

    task = gevent.spawn(crawler)

    #用gevent.spawn()函數創建執行crawler()函數的任務

    tasks_list.append(task)

    #往任務列表添加任務。

gevent.joinall(tasks_list)

#用gevent.joinall方法,執行任務列表裏的所有任務,就是讓爬蟲開始爬取網站

end = time.time()

print(end-start)

更強大的爬蟲

當你爬蟲代碼越寫越多,重複性的動作越來越多時,一個一站式解決所有爬蟲問題的框架,就對你越來越有吸引力。

Scrapy出現在你眼前。Scrapy的結構——
在這裏插入圖片描述Scrapy的用法——
在這裏插入圖片描述

更進一步

爲了能讓我們能更好的使用爬蟲,我們還穿插學習了三個有力工具:selenium,郵件通知和定時。

先說selenium,我們學習了使用.get(‘URL’)獲取數據,以及解析與提取數據的方法。

在這裏插入圖片描述
在這個過程中,對象的轉換過程:
在這裏插入圖片描述
使用selenium時,還可以搭配BeautifulSoup解析提取數據,前提是先獲取字符串格式的網頁源代碼。

HTML = driver.page_source 

以及如何使用selenium自動操作瀏覽器。
在這裏插入圖片描述
而關於郵件,它是這樣一種流程:
在這裏插入圖片描述
我們要用到的模塊是smtplib和email,前者負責連接服務器、登錄、發送和退出的流程。後者負責填輸郵件的標題與正文。

import smtplib
from email.mime.text import MIMEText
from email.header import Header
#引入smtplib、MIMETex和Header

mailhost='smtp.qq.com'
#把qq郵箱的服務器地址賦值到變量mailhost上,地址應爲字符串格式
qqmail = smtplib.SMTP()
#實例化一個smtplib模塊裏的SMTP類的對象,這樣就可以調用SMTP對象的方法和屬性了
qqmail.connect(mailhost,25)
#連接服務器,第一個參數是服務器地址,第二個參數是SMTP端口號。
#以上,皆爲連接服務器。

sender = input('請輸入你的郵箱:')
#獲取郵箱賬號,爲字符串格式
password = input('請輸入你的密碼:')
#獲取郵箱密碼,爲字符串格式
qqmail.login(sender,password)
#登錄郵箱,第一個參數爲郵箱賬號,第二個參數爲郵箱密碼
#以上,皆爲登錄郵箱。

receiver=input('請輸入收件人的郵箱:')
#獲取收件人的郵箱。

content=input('請輸入郵件正文:')
#輸入你的郵件正文,爲字符串格式
message = MIMEText(content, 'plain', 'utf-8')
#實例化一個MIMEText郵件對象,該對象需要寫進三個參數,分別是郵件正文,文本格式和編碼
subject = input('請輸入你的郵件主題:')
#輸入你的郵件主題,爲字符串格式
message['Subject'] = Header(subject, 'utf-8')
#在等號的右邊是實例化了一個Header郵件頭對象,該對象需要寫入兩個參數,分別是郵件主題和編碼,然後賦值給等號左邊的變量message['Subject']。
#以上,爲填寫主題和正文。

try:
    qqmail.sendmail(sender, receiver, message.as_string())
    print ('郵件發送成功')
except:
    print ('郵件發送失敗')
qqmail.quit()
#以上爲發送郵件和退出郵箱

關於如何定時啓動爬蟲,我們選取了schedule模塊,它的用法非常簡潔。

import schedule
import time
#引入schedule和time

def job():
    print("Working in progress...")
    #定義一個叫job的函數,函數的功能是打印'Working in progress...'

#部署情況
schedule.every(10).minutes.do(job) #部署每10分鐘執行一次job()函數的任務
schedule.every().hour.do(job) #部署每×小時執行一次job()函數的任務
schedule.every().day.at("10:30").do(job) #部署在每天的10:30執行job()函數的任務
schedule.every().monday.do(job) #部署每個星期一執行job()函數的任務
schedule.every().wednesday.at("13:15").do(job)#部署每週三的13:15執行函數的任務

while True:
    schedule.run_pending()
    time.sleep(1)
    #檢查部署的情況,如果任務準備就緒,就開始執行任務

二、爬蟲進階路線

這些內容對於爬蟲來說,僅僅算是入門。接着我們會談談,如何繼續深入地學習爬蟲。

按照之前我們學習爬蟲的路徑,我把進階指引分爲這幾個板塊:解析與提取、存儲、數據分析與可視化(新增)、更多的爬蟲、框架,以及其他。

解析與提取

先說數據的解析和提取,這裏指學習新的解析庫。除了我們關卡里所用的BeautifulSoup解析、Selenium的自帶解析庫之外,還會有:xpath/lxml等。它們可能語法有所不同,但底層原理都一致,有了之前的基礎,加上一些實戰練習,相信你能輕鬆的掌握它們。

另外,強烈地推薦去學習正則表達式(re模塊)。正則表達式功能強大,它能讓你自己設定一套複雜的規則,然後把目標文本里符合條件的相關內容給找出來。比如說我們在第6課中爬取的周杰倫的歌詞數據中,除了歌詞外還有很多其他的符號。我們之前在課程中學到的方法是一遍一遍的使用「切片」來處理。但是如果你學習了正則表達式,那麼提取歌詞就變得異常輕鬆。

存儲

說完了數據的獲取,接下來就該談談如何存儲存儲。我們目前已經掌握的知識是csv和excel。掌握該如何使用它們並不難,要了解更多的用法,我推薦你翻閱它們的官方文檔。

但是,這隻適用於存儲少量的數據。數據量變得海量時(事實上也是如此),尤其是涉及數據與數據之間的關係是,我們是難以用一張簡單的二維平面表格來承載大量的數據的。

此時,推薦使用數據庫。你可以先從MySQL和MongoDB這兩種數據庫開始。它們一個是關係型數據庫的典型代表,一個是非關係型數據庫的典型代表。

要掌握數據庫,你需要學習另一種語言:SQL。

數據分析與可視化

徒有海量的原始數據的價值非常有限。數據,要被分析過才能創造出更深遠的價值。舉個例子:拿到全中國萬達廣場的地址和營收數據意義並不大,但如果能從中總結出大型商超經營的最優策略,這就能指導商業決策。

這樣的技能被稱爲數據分析。將數據分析的結論,直觀、有力地傳遞出來,就是我們經常提到的數據可視化。

嚴格的說,學習數據分析,要比爬蟲還要花費更多的時間。因爲我們不僅要知道數據本身所描述的現象,還要深入挖掘數據背後所帶有含義。這裏推薦的模塊與庫:Pandas/Matplotlib/Numpy/Scikit-Learn/Scipy。

更多爬蟲

當爬取的內容變多時,協程可以使爬蟲的速度得到顯著的提升。

嚴格來說這並不是同時工作,而是電腦在多個任務之間快速地來回切換,看上去就彷彿是爬蟲們同時工作。因此協成給爬蟲速度帶來的優化是有上限的。

如何突破這樣的上限,就要用到多進程了。我們已經知道,協程在本質上只用到CPU的一個核。而多進程(multiprocessing庫)爬蟲允許你使用CPU的多個核,所以你可以使用多進程,或者是多進程與多協程結合的方式進一步優化爬蟲。

理論上來說,只要CPU允許,開多少個進程,就能讓你的爬蟲速度提高多少倍。但這種方式會因受到CPU核數的限制。

如何實現進一步的突破呢?那我們就要用到分佈式爬蟲了:即用多個設備,去跑同一個項目。

這樣一來,多加設備就能提升爬蟲速度。很多企業在面對大量爬蟲任務時就是使用分佈式的方式來進行爬蟲作業。

而實現分佈式爬蟲,需要下一個組塊的內容——框架。

更強大的爬蟲——框架

我們已經簡單地學過Scrapy框架的基本原理和用法。而一些更深入的用法:使用Scrapy模擬登錄、存儲數據庫、使用HTTP代理、分佈式爬蟲……這些就還不曾涉及。

在深入地學習和了解Scrapy框架的基礎之上,你還可以瞭解一些其他的優秀框架,例如PySpider。

三、反爬蟲應對策略簡要彙總

幾乎所有的技術人員對反爬蟲都有一個共識:所謂的反爬蟲,從不是將爬蟲完全杜絕;而是想辦法將爬蟲的訪問量限制在一個可接納的範圍,不要讓它過於肆無忌憚。

原因很簡單:爬蟲代碼寫到最後,服務器無法真正區分是人還是爬蟲。如果想要完全禁止爬蟲,正常用戶也會無法訪問。所以只能想辦法進行限制,而非禁止。

因此我們可以瞭解“反爬蟲”的策略是怎樣的,再思考如何應對“反爬蟲”。

  • 填寫user-agent聲明自己的身份,以此來應對網站對請求頭的限制,即Request Headers,有時還要去填寫origin和referer聲明請求的來源。
  • 可以用cookies和session的知識去模擬登錄,來應對網站對登錄的限制。
  • 有的網站會做一些複雜的交互,比如設置“驗證碼”。解決方案也各有不同,例如:我們用Selenium去手動輸入驗證碼;我們用一些圖像處理的庫自動識別驗證碼(tesserocr/pytesserart/pillow)。
  • 有的網站則會限制IP。當我們的IP地址爬取網站的頻次太高,那麼服務器就會暫時封掉來自這個IP地址的請求。

解決方案例如:使用time.sleep()來對爬蟲的速度進行限制;建立IP代理池(你可以在網絡上搜索可用的IP代理),一個IP不能用了就換一個用。

import requests

url = 'https://…'

proxies = {'http':'http://…'}

# ip地址

response = requests.get(url,proxies=proxies)

四、小結

我們學習過的所有模塊、庫或者框架都是易於掌握的工具,只要經過一定時間的學習和一定量的訓練你就能掌握。對於一個爬蟲工程師來說最重要的,是達成目標的思維。

在我們所有的項目型關卡里,在我們開始編寫我們代碼之前,都遵循着這三個核心步驟:確認目標、分析過程和代碼實現(對於複雜項目,也要去做代碼封裝)。

其中,難度最大的就是確認一個合理的目標,分析這個目標如何實現,設計這些工具模塊如何組合應用。

我們在編寫爬蟲代碼時所遵循的四個步驟:獲取數據、解析數據、提取數據和存儲數據,都是建立在“分析過程”的基礎之上的。

要養成這樣的思維模式,我們需要大量的項目實操練習。

因此,建議在完成項目練習的基礎之上,多讀一讀編程前輩們的博客、github主頁學習案例;同時,探索更多的項目實操,豐富自己的爬蟲經驗。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章