個人第一個Python爬蟲——談心得

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

先給你們分享一門我以前看過的課程,挺不錯的,免費分享給你們前端

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

須要這門教程的小夥伴可點擊進入扣羣下載直達連接:https://jq.qq.com/?_wv=1027&k=55fzJrTweb

接下來繼續分享我所要講解的內容正則表達式

1、作爬蟲所須要的基礎

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

 

 

 

以後會用到這種思想來在一大堆HTML字符串中找出咱們想要的東西。瀏覽器

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

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

瞭解了這兩點就能夠準備工具了,固然,對爬蟲有興趣還能夠了解一下爬蟲的發展史。cookie

2、介紹幾款優秀製做爬蟲的輔助工具

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

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

3、最簡單的爬蟲試例

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

 

 

 見到花花綠綠的HTML代碼不要懼怕,一個一個點,直到找到須要的信息就好了,能夠看到全部電影名都是在這樣

 <div class = "pl2">

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

 

 

 抓取結果以下:

 

 

 

乍一看,就這麼個玩意兒,這些電影名還不如直接本身去網頁看,這有什麼用呢?可是,你想一想,只要你掌握了這種方法,若是有翻頁你能夠按照規則爬完了一頁就解析另一頁HTML(一般翻頁的時候URL會規律變化,也就是GET請求實現的翻頁),也就是說,只要掌握的爬取方法,不管工做量有多麼大均可以按你的心思去收集想要的數據了。

4、須要模擬登陸後再爬取的爬蟲所須要的信息
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:

 

 

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

   1、登陸後的網頁和服務器創建了聯繫,因此能和服務器進行通訊,但即便你從這個網頁點擊裏面的超連接跳轉到另一個子網頁,在新網頁中仍是保持登陸狀態的在不斷的跳轉中是怎麼識別用戶的呢?

   在這裏,服務器端通常是採用的Cookie技術,登錄後給你一個Cookie,之後你發出跳轉網頁的請求就攜帶該Cookie,服務器就能知道是你在哪以什麼狀態點擊的該頁面,也就解決了HTTP傳輸的無狀態問題。

   很明顯,在模擬登陸之後保持登陸狀態須要用得着這個Cookie,固然Cookie在請求頭中是可見的,爲了本身的帳號安全,請不要輕易暴露/泄漏本身的Cookie

 

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

 

 

   3、查找表單隱藏參數的獲取方式,在上文表單列表中有個lt參數,雖然我也不知道他是幹嗎的,但經過POST傳輸過去的表單確定是會通過服務器驗證的,因此須要弄到這份參數,而這份參數通常都會在HTML頁面中由JS腳本自動生成,能夠由Beautifulsoup自動解析抓取。  

  

關於Fiddler的使用和實戰教程能夠查看連接:https://www.jianshu.com/p/efefcbc605e8

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

 

5、開始編碼爬蟲

若是用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

6、爬蟲技術的拓展與提升

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

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

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

 

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

 

7、後記

  幾天後我發現了另外一個格式較好的頁面,因而去爬那個網站,結果他是.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好用太多了。 以下圖:

 

 

 總之,通過這段時間的嘗試,我對爬蟲也有了個初步的瞭解,在這方面,也有了本身作法:

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

 

 

 

 

 

 

 

 

 

相關文章
相關標籤/搜索