我的第一個Python爬蟲——談心得


2019年3月27日,繼開學到現在以來,開了軟件工程和信息系統設計,想來想去也沒什么好的題目,干脆就想弄一個實用點的,於是產生了做“學生服務系統”想法。相信各大高校應該都有本校APP或超級課程表之類的軟件,在信息化的時代能快速收集/查詢自己想要的咨詢也是種很重要的能力,所以記下了這篇博客,用於總結我所學到的東西,以及用於記錄我的第一個爬蟲的初生

Python爬蟲工程師必學 App數據抓取實戰,內容官網https://coding.imooc.com/class/283.html

可以搜索887934385交流群,進入后下載資料工具安裝包等。

接下來繼續分享所要講解的內容

一、做爬蟲所需要的基礎

要做一只爬蟲,首先就得知道他會干些什么,是怎樣工作的。所以得有一些關於HTML的前置知識,這一點做過網頁的應該最清楚了。
   HTML(超文本標記語言),是一種標記性語言,本身就是一長串字符串,利用各種類似 < a >,< /a>這樣的標簽來識別內容,然后通過瀏覽器的實現標准來翻譯成精彩的頁面。當然,一個好看的網頁並不僅僅只有HTML,畢竟字符串是靜態的,只能實現靜態效果,要作出漂亮的網頁還需要能美化樣式的CSS和實現動態效果的JavaScipt,只要是瀏覽器都是支持這些玩意兒的。
   嗯,我們做爬蟲不需要了解太多,只需要了解HTML是基於文檔對象模型(DOM)的,以樹的結構,存儲各種標記,就像這樣:

 

 

 

之后會用到這種思想來在一大堆HTML字符串中找出我們想要的東西。

了解了這個然后還得了解網頁和服務器之間是怎么通信的,這就得稍微了解點HTTP協議,基於TCP/IP的應用層協議,規定了瀏覽器和服務器之間的通信規則,簡單粗暴的介紹幾點和爬蟲相關的就是:

瀏覽器和服務器之間有如下幾種通信方式:
   GET:向服務器請求資源,請求以明文的方式傳輸,一般就在URL上能看到請求的參數
   POST:從網頁上提交表單,以報文的形式傳輸,請求資源
   還有幾種比較少見就不介紹了。

了解了這兩點就可以准備工具了,當然,對爬蟲有興趣還可以了解一下爬蟲的發展史。

二、介紹幾款優秀制作爬蟲的輔助工具

由於我是采用python3.6開發的,然后從上文的介紹中,也該知道了一只爬蟲是需要從HTML中提取內容,以及需要和網頁做交互等。
   如果不采用爬蟲框架的話,我建議采用:
   
    BeautifulSoup 庫 ,一款優秀的HTML/XML解析庫,采用來做爬蟲,
              不用考慮編碼,還有中日韓文的文檔,其社區活躍度之高,可見一斑。
              [注] 這個在解析的時候需要一個解析器,在文檔中可以看到,推薦lxml
              
    Requests 庫,一款比較好用的HTTP庫,當然python自帶有urllib以及urllib2等庫,
            但用起來是絕對沒有這款舒服的,哈哈
           
    Fiddler. 工具,這是一個HTTP抓包軟件,能夠截獲所有的HTTP通訊。
          如果爬蟲運行不了,可以從這里尋找答案,官方鏈接可能進不去,可以直接百度下載

爬蟲的輔助開發工具還有很多,比如Postman等,這里只用到了這三個,相信有了這些能減少不少開發阻礙。

三、最簡單的爬蟲試例

最簡單的爬蟲莫過於單線程的靜態頁面了,這甚至都不能叫爬蟲,單單一句正則表達式即可匹配出所有內容,比如各種榜單:豆瓣電影排行榜,這類網站爬取規則變化比較少,用瀏覽器自帶的F12的審查很容易找到需要爬取信息的特征:

 

 

 見到花花綠綠的HTML代碼不要害怕,一個一個點,直到找到需要的信息就行了,可以看到所有電影名都是在這樣

 <div class = "pl2">

之下的,每有一個這樣的標簽就代表一個電影,從他的孩子< span >中即可抓取到電影名。
代碼如下:

 

 

 抓取結果如下:

 

 

 

乍一看,就這么個玩意兒,這些電影名還不如直接自己去網頁看,這有什么用呢?但是,你想想,只要你掌握了這種方法,如果有翻頁你可以按照規則爬完了一頁就解析另外一頁HTML(通常翻頁的時候URL會規律變化,也就是GET請求實現的翻頁),也就是說,只要掌握的爬取方法,無論工作量有多么大都可以按你的心思去收集想要的數據了。

四、需要模擬登錄后再爬取的爬蟲所需要的信息
4.1.登錄分析
剛才的爬蟲未免太簡單,一般也不會涉及到反爬蟲方面,這一次分析需要登錄的頁面信息的爬取,按照往例,首先打開一個網頁:
    我選擇了我學校信息服務的網站,登錄地方的代碼如下:

 

 

 

可以看到驗證碼都沒有,就只有賬號密碼以及提交。光靠猜的當然是不行的,一般輸入密碼的地方都是POST請求。
    POST請求的響應流程就是 客戶在網頁上填上服務器准備好的表單並且提交,然后服務器處理表單做出回應。一般就是用戶填寫帳號、密碼、驗證碼然后把這份表單提交給服務器,服務器從數據庫進行驗證,然后作出不同的反應。在這份POST表單中可能還有一些不需要用戶填寫的用腳本生成的隱藏屬性作為反爬蟲的手段。
    要知道表單格式可以先試着隨便登錄一次,然后在F12中的network中查看登錄結果,如圖:

 

 

                                                                                                                                           圖1

 

 

                                                                                                                                            圖2

【注】如果用真正的賬號密碼登錄,要記住勾選上面的Preserve log,這樣即使網頁發生了跳轉之前的信息也還在。
從上面的兩張圖中很容易發現其中的一個POST請求, login?serv…就是登錄請求了
可以看到這個登錄請求所攜帶的信息有:
General: 記錄了請求方式,請求地址,以及服務器返回的狀態號 200等
Response Headers: 響應頭,HTTP響應后傳輸的頭部消息
Request Headers: 請求頭,重點!!,向服務器發送請求時,發出的頭部消息,之中很多參數都是爬蟲需要模擬出來傳送給服務器的。
From Data:表單,重點!!,在這里表單中有:

 

 

 

我明明都填的12345,為什么密碼變了呢?可以看出這密碼不是原始值,應該是編碼后的產物,網站常用的幾種編碼/加密方法就幾種,這里是采用的base64編碼,如果對密碼編碼的方式沒有頭緒可以仔細看看登錄前后頁面的前端腳本。運氣好可以看到encode函數什么的。

4.2信息提取
如果了解過Resquests庫的文檔就知道,發送一個一般的POST請求所需要的參數構造是這樣的:

 

 

 

從上面的兩張圖片中即可找到發送一個正確的請求所需要的參數,即 url 和 data :
   url 即上面的 Request URL:
Request URL: http://uia.hnist.cn/sso/login?service=http%3A%2F%2Fportal.hnist.cn%2Fuser%2FsimpleSSOLogin
   data 即上面的From data:

 

 

 收集到了必要的信息還得了解三點:

   一、登錄后的網頁和服務器建立了聯系,所以能和服務器進行通信,但即使你從這個網頁點擊里面的超鏈接跳轉到另外一個子網頁,在新網頁中還是保持登錄狀態的在不斷的跳轉中是怎么識別用戶的呢?

   在這里,服務器端一般是采用的Cookie技術,登陸后給你一個Cookie,以后你發出跳轉網頁的請求就攜帶該Cookie,服務器就能知道是你在哪以什么狀態點擊的該頁面,也就解決了HTTP傳輸的無狀態問題。

   很明顯,在模擬登錄以后保持登錄狀態需要用得着這個Cookie,當然Cookie在請求頭中是可見的,為了自己的賬號安全,請不要輕易暴露/泄漏自己的Cookie

 

二、先了解一下,用python程序訪問網頁的請求頭的User-Agent是什么樣的呢?沒錯,如下圖所示,很容易分辨這是程序的訪問,也就是服務器知道這個請求是爬蟲訪問的結果,如果服務器做了反爬蟲措施程序就會訪問失敗,所以需要程序模擬瀏覽器頭,讓對方服務器認為你是使用某種瀏覽器去訪問他們的。

 

 

   三、查找表單隱藏參數的獲取方式,在上文表單列表中有個lt參數,雖然我也不知道他是干嘛的,但通過POST傳輸過去的表單肯定是會經過服務器驗證的,所以需要弄到這份參數,而這份參數一般都會在HTML頁面中由JS腳本自動生成,可以由Beautifulsoup自動解析抓取。  

  

關於Fiddler的使用和實戰教程可以查看鏈接:https://www.jianshu.com/p/efefcbc605e8

嗯,最重要的幾樣東西已經收集完畢,對Cookie和請求頭的作用也有了個大概的了解,然后開始發送請求試試吧~

 

五、開始編碼爬蟲

如果用urllib庫發送請求,則需要自己編碼Cookie這一塊(雖然也只要幾行代碼),但用Requests庫就不需要這樣,在目前最新版本中,requests.Session提供了自己管理Cookie的持久性以及一系列配置,可以省事不少。

   先以面對過程的方式實驗地去編碼:

from bs4 import BeautifulSoup

from lxml import html

import requests

####################################################################################

#  在這先准備好請求頭,需要爬的URL,表單參數生成函數,以及建立會話

############################# 1 #################################################

header={

    "Accept": "text/html, application/xhtml+xml, image/jxr, */*",

    "Referer": "http://uia.hnist.cn/sso/login?service=http%3A%2F%2Fportal.hnist.\

    cn%2Fuser%2FsimpleSSOLogin",    

    "Accept-Language": "zh-Hans-CN,zh-Hans;q=0.8,en-US;q=0.5,en;q=0.3",

    "Content-Type": "application/x-www-form-urlencoded",

    "Accept-Encoding": "gzip, deflate",

    "Connection": "Keep-Alive",

    "User-Agent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) \

 AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.162 Safari/537.36",

    "Accept-Encoding": "gzip, deflate",

    "Origin": "http://uia.hnist.cn",

    "Upgrade-Insecure-Requests": "1",

   #Cookie由Session管理,這里不用傳遞過去,千萬不要亂改頭,我因為改了頭的HOST坑了我兩天

}  

 

School_login_url = 'http://uia.hnist.cn/sso/login? \

service=http%3A%2F%2Fportal.hnist.cn%2Fuser%2FsimpleSSOLogin'#學校登錄的URL

 

page = requests.Session()     #用Session發出請求能自動處理Cookie等問題

page.headers = header #為所有請求設置頭

page.get(School_login_url)    #Get該地址建立連接(通常GET該網址后,服務器會發送一些用於\

驗證的參數用於識別用戶,這些參數在這就全由requests.Session處理了)

 

 

def Get_lt():#獲取參數 lt 的函數

    f = requests.get(School_login_url,headers = header)

    soup = BeautifulSoup(f.content, "lxml")  

    once = soup.find('input', {'name': 'lt'})['value']

    return once

 

lt = Get_lt()  #獲取lt

 

From_Data = {   #表單

    'username': 'your username',

    'password': 'Base64 encoded password',   

    #之前說過密碼是通過base64加密過的,這里得輸入加密后的值,或者像lt一樣寫個函數

    'lt': lt,

    '_eventId': 'submit',

}

############################# 1 end #############################

 

################################################################

#  在這一段向登錄網站發送POST請求,並判斷是否成功返回正確的內容

############################# 2 #################################

 

q = page.post(School_login_url,data=From_Data,headers=header) 

#發送登陸請求

 

#######查看POST請求狀態##############

#print(q.url)#這句可以查看請求的URL

#print(q.status_code)  #這句可以查看請求狀態

#for (i,j) in q.headers.items():

#    print(i,':',j)#這里可以查看響應頭

#print('\n\n')

#for (i,j) in q.request.headers.items():

#    print(i,':',j)#這里可以查看請求頭

####上面的內容用於判斷爬取情況,也可以用fiddle抓包查看 ####

 

f = page.get('http://uia.hnist.cn')#GET需要登錄后(攜帶cookie)才能查看的網站

print("body:",f.text)

 

######## 進入查成績網站,找到地址,請求並接收內容 #############

 

proxies = {  #代理地址,這里代理被注釋了,對后面沒影響,這里也不需要使用代理....

#"http": "http://x.x.x.x:x",

#"https": "http://x.x.x.x:x",

}

 

########  查成績網站的text格式表單,其中我省略了很多...######

str = """callCount=1

httpSessionId=DA0080E0317A1AD0FDD3E09E095CB4B7.portal254

scriptSessionId=4383521D7E8882CB2F7AB18F62EED380

page=/web/guest/788

"""

#### 這是由於該服務器關於表單提交部分設計比較垃圾,所以不用去在意表單內容含義 ###

 

f = page.post('http://portal.hnist.cn/portal_bg_ext/dwr/plainjs/

ShowTableAction.showContent.dwr',\data=str,proxies=proxies)

 #查成績的地址,表單參數為上面的str

  

######  查看地址,返回狀態,以及原始內容#######"""

print("f:",f.url)

print(f.status_code)

text = f.content.decode('unicode_escape')

print(text.encode().decode()) #因為原始內容中有\uxxx形式的編碼,所以使用這句解碼

###########################################"""

################################### 2 end #########################

 

###################################################################

#  解析獲得的內容,並清洗數據,格式化輸出...

############################# 3 ####################################

[注] 如果使用了Fiddler,他會自動為Web的訪問設置一個代理,這時候如果你關閉了Fiddler可能爬蟲會無法正常工作,這時候你選擇瀏覽器直連,或者設置爬蟲的代理為Fiddler即可。

[注2]爬蟲不要頻率太快,不要影響到別人服務器的正常運行,如果不小心IP被封了可以使用代理(重要數據不要使用不安全的代理),網上有很多收費/免費的代理,可以去試下。

 

過程中獲得的經驗:

 

在上面第一部分,不知道作用的參數不要亂填,只需要填幾個最重要的就夠了,比如UA,有時候填了不該填的請求將會返回錯誤狀態.,盡量把可分離的邏輯寫成函數來調用,比如生成的表單參數,加密方法等.

在上面第二部分如果請求失敗可以配合抓包軟件查看程序和瀏覽器發送的請求有什么差別,遺漏了什么重要的地方,盡量讓程序模仿瀏覽器的必要的行為。

第三部分中,因為拿到的數據是如下圖1這樣的,所以需要最后輸出后decode,然后再使用正則表達式提取出雙引號中的內容連接誒成一個標記語言的形式,再使用Beautifulsoup解析獲得需要的數據,如圖2.

中途可能利用的工具有:

官方正則表達式學習網站

HTML格式美化

正則表達式測試

 

 

                                                    圖1        

 

 

                                                                                                                       圖2

六、爬蟲技術的拓展與提高

  經歷了困難重重,終於得到了想要的數據,對於異步請求,使用JS渲染頁面后才展示數據的網頁,又或是使用JS代碼加密過的網頁,如果花時間去分析JS代碼來解密,簡單的公有的加密方法倒是無所謂,但對於特別難的加密就有點費時費力了,在要保持抓取效率的情況下可以使用能使用Splash框架:

  這是一個Javascript渲染服務,它是一個實現了HTTP API的輕量級瀏覽器,Splash是用Python實現的,同時使用Twisted和QT。Twisted(QT)用來讓服務具有異步處理能力,以發揮webkit的並發能力。

  就比如像上面返回成績地址的表單參數,格式為text,並且無規律,有大幾十行,如果要弄明白每個參數是什么意思,還不如加載瀏覽器的JS 或 使用瀏覽器自動化測試軟件來獲取HTML了,所以,遇到這種情況,在那么大一段字符串中,只能去猜哪些參數是必要的,哪些參數是不必要的,比如上面的,我就看出兩個是有關於返回頁面結果的,其余的有可能存在驗證身份的,時間的什么的。

 

  對於信息的獲取源,如果另外的網站也有同樣的數據並且抓取難度更低,那么換個網站爬可能是個更好的辦法,以及有的網站根據請求頭中的UA會產生不同的布局和處理,比如用手機的UA可能爬取會更加簡單。

 

七、后記

  幾天后我發現了另一個格式較好的頁面,於是去爬那個網站,結果他是.jsp的,采用之前的方法跳轉幾個302之后就沒有后續了…后來才猜想了解到,最后一個302可能是由JS腳本跳轉的,而我沒有執行JS腳本的環境,也不清楚他執行的哪個腳本,傳入了什么參數,於是各種嘗試和對比,最后發現:正常請求時,每次都多2個Cookie,開始我想,Cookie不是由Session管理不用去插手的嗎?然后我想以正常方式獲得該Cookie,請求了N個地址,結果始終得不到想要的Cookie,於是我直接使用Session.cookies.set('COMPANY_ID','10122')添加了兩個Cookie,還真成了…神奇…

  當然,過了一段時間后,又不行了,於是仔細觀察,發現每次就JSESSIONID這一個Cookie對結果有影響,傳遞不同的值到不同的頁面還…雖然我不認同這種猜的,毫無邏輯效率的瞎試。但經歷長時間的測試和猜測,對結果進行總結和整理也是能發現其中規律的。

 

  關於判斷某動作是不是JS,可以在Internet選項中設置禁止使用JS

 

  關於失敗了驗證的方法,我強烈建議下載fiddler,利用新建視圖,把登錄過程中所有的圖片,CSS等文件去掉以后放到新視圖中,然后利用程序登錄的過程也放一個視圖當中,如果沒有在響應中找到需要的Cookie,還可以在視圖中方便的查看各個JS文件,比瀏覽器自帶的F12好用太多了。 如下圖:

 

 

 總之,經過這段時間的嘗試,我對爬蟲也有了個初步的了解,在這方面,也有了自己做法:

  
抓包請求 —> 模仿請求頭和表單—>如果請求失敗,則仔細對比正常訪問和程序訪問的數據包 —>成功則根據內容結構進行解析—>清清洗數據並展示

 


免責聲明!

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



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