我看了許多教程,以前練習過爬取百度百科和汽車之家,都沒有成功,可能功力不到,有些問題總看不出究竟是哪裏出了毛病。html
我看得其中有一個教程是作post請求爬取的,我拖拖拉拉的作了許久,剛好爬到數據了,所以特意發個文記念一下。html5
聲明一個在不少教程裏面都看到的內容,不要頻繁的、大批量的使用代碼去訪問別人的網站,服務器是一種很貴的資源。python
步驟一:嘗試爬取數據mysql
# urllib_post.py # -*- coding:utf-8 -*- from urllib.request import urlopen from urllib.request import Request from urllib import parse req = Request("https://kuai.baidu.com/pc/schedule/schedulelist") postData = parse.urlencode([ ("startcityid", 224), ("arrivalcityid", 289), ("startdatetime", 1489507200), ("us", "pc"), ("hmsr", ""), ("hmmd", ""), ("hmpl", ""), ("hmkw", ""), ("hmci", ""), ]) print(postData) req.add_header("Referer","https://kuai.baidu.com/pc") req.add_header("User-Agent","Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/50.0.2661.102 Safari/537.36") resp = urlopen(req, data=postData.encode(encoding='utf-8'))#bytes(postData, "utf-8") print(resp.read().decode("utf-8"))
個人版本是Python3.5,使用urllib庫請求網頁數據。聽說Python2.x版本的urllib庫是分爲urllib和urllib2的,urllib2是urllib的升級,可是兩者的方法卻不重合,常常一塊兒使用。sql
在請求網頁內容的時候,使用了.add_header添加請求頭,假裝成瀏覽器進行請求,這樣不容易被爬取的網站拒絕。數據庫
直接用瀏覽器請求網站在控制檯(F12→Network→Header)裏面看到的是下面這樣兒的:json
這裏我有一個疑問,就是看到Request Method:GET我忽然不知道本身是作的get請求仍是post請求了。瀏覽器
假裝瀏覽器須要用到的主要就是User-Agent。服務器
這個是跟在請求連接後面的參數,具體該怎麼叫我忘了。網絡
另外,在寫代碼的時候我還遇到一個麻煩,urlopen(req,data)的data參數顯示應該爲二進制編碼,我直接傳入string類型報錯。這裏有2中方法能夠解決:1.string.encode(encoding='utf-8'));2.bytes(string, "utf-8")。
步驟二:使用BeautifulSoup處理數據並存儲
# urllib_post_20170327.py # -*- coding:utf-8 -*- from urllib.request import urlopen from urllib.request import Request from urllib import parse from bs4 import BeautifulSoup import json req = Request("https://kuai.baidu.com/pc/schedule/schedulelist") # 給請求添加一些Heather頭部 req.add_header("Referer","https://kuai.baidu.com/pc") req.add_header("User-Agent","Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/50.0.2661.102 Safari/537.36") def get_one(page=0,schedulelist=[]): # 初始化時刻表 if page == 0: schedulelist=[] # 轉換post數據 postData = parse.urlencode([ ("startcityid", 224),# 224 蘇州 ("arrivalcityid", 289),# 289 上海 ("startdatetime", 1490630400),# 時間,不知道這個時間網站是怎麼計算的 ("us", "pc"), ("hmsr", ""), ("hmmd", ""), ("hmpl", ""), ("hmkw", ""), ("hmci", ""), ("page", page) ]) # print(postData) resp = urlopen(req, data=postData.encode(encoding='utf-8'))#bytes(postData, "utf-8") # print(resp.read().decode("utf-8")) # 轉換成BeautifulSoup進行處理 bs = BeautifulSoup(resp,"lxml") if bs.find("ul",{"class":"bus-ls"}) and len(bs.find("ul",{"class":"bus-ls"}).find_all("li",{"class":"clearfix"}))>0: bs_schedulelist = bs.find("ul",{"class":"bus-ls"}).find_all("li",{"class":"clearfix"}) for item in bs_schedulelist: schedule = {} schedule["startTime"] = item.find("p",{"class":"start-time-text"}).get_text() schedule["startStation"] = item.find("p",{"class":"start-station"}).get_text() schedule["arriveStation"] = item.find("p",{"class":"arrive-station"}).get_text() schedule["busLevel"] = item.find("div",{"class":"bus-lv"}).get_text() schedule["ticketPrice"] = item.find("div",{"class":"ticket-price"}).get_text() schedulelist.append(schedule) print(len(schedulelist)) # 遞歸 get_one(page=page+1, schedulelist=schedulelist) else: # 當不知足條件時返回內容,跳出遞歸 print("獲取完成") print(json.dumps(schedulelist)) return return json.dumps(schedulelist) # 直接運行當前文件時執行的內容 if __name__== "__main__": schedulelist=get_one() print(schedulelist) # 文件操做 fs = open("schedulelist.txt", "w") fs.write(schedulelist) fs.close()
Beautiful Soup 是一個能夠從HTML或XML文件中提取數據的Python庫.它可以經過你喜歡的轉換器實現慣用的文檔導航,查找,修改文檔的方式.Beautiful Soup會幫你節省數小時甚至數天的工做時間.
下表列出了主要的解析器,以及它們的優缺點:(這個是從BeautifulSoup官方文檔粘過來的)
解析器 | 使用方法 | 優點 | 劣勢 |
---|---|---|---|
Python標準庫 | BeautifulSoup(markup, "html.parser") |
|
|
lxml HTML 解析器 | BeautifulSoup(markup, "lxml") |
|
|
lxml XML 解析器 | BeautifulSoup(markup, ["lxml", "xml"]) BeautifulSoup(markup, "xml") |
|
|
html5lib | BeautifulSoup(markup, "html5lib") |
|
|
解析器使用pip install進行下載。
我使用的是lxml HTML 解析器,緣由簡單粗暴,由於我使用BeautifulSoup解析html的時候沒有傳入解析器對應的參數,報錯了以後提示我能夠這麼用【BeautifulSoup(markup, "lxml")】。
我在處理分頁的時候用了遞歸,其實並不用這麼作,循環多是更好的方式。對於初識遞歸光環的我,好不容易能找個地方用一下,天然不會錯過。遞歸的時候也出了一點問題,就是我return返回的數據,本來個人return json.dumps(schedulelist)是寫在else裏面的,結果致使最外層的get_one()沒有返回任何數據,經大神提點,改爲了代碼如今的樣子。不過我內心仍是以爲本身寫的這個遞歸有點問題,至於哪裏有問題,可能還得更深刻的學習才知道。
步驟三:使用數據庫存儲得到的數據
# -*- coding:utf-8 -*- from urllib.request import urlopen from urllib.request import Request from urllib import parse from bs4 import BeautifulSoup import json import pymysql req = Request("https://kuai.baidu.com/pc/schedule/schedulelist") # 給請求添加一些Heather頭部 req.add_header("Referer","https://kuai.baidu.com/pc") req.add_header("User-Agent","Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/50.0.2661.102 Safari/537.36") def get_one(page=0,schedulelist=[]): # 初始化時刻表 if page == 0: schedulelist=[] # 轉換post數據 postData = parse.urlencode([ ("startcityid", 224),# 224 上海 ("arrivalcityid", 289),# 289 蘇州 ("startdatetime", 1491062400), ("us", "pc"), ("hmsr", ""), ("hmmd", ""), ("hmpl", ""), ("hmkw", ""), ("hmci", ""), ("page", page) ]) # print(postData) resp = urlopen(req, data=postData.encode(encoding='utf-8'))#bytes(postData, "utf-8") # print(resp.read().decode("utf-8")) # 轉換成BeautifulSoup進行處理 bs = BeautifulSoup(resp,"lxml") if bs.find("ul",{"class":"bus-ls"}) and len(bs.find("ul",{"class":"bus-ls"}).find_all("li",{"class":"clearfix"}))>0: bs_schedulelist = bs.find("ul",{"class":"bus-ls"}).find_all("li",{"class":"clearfix"}) for item in bs_schedulelist: schedule = {} schedule["startTime"] = item.find("p",{"class":"start-time-text"}).get_text() schedule["startStation"] = item.find("p",{"class":"start-station"}).get_text() schedule["arriveStation"] = item.find("p",{"class":"arrive-station"}).get_text() schedule["busLevel"] = item.find("div",{"class":"bus-lv"}).get_text() schedule["ticketPrice"] = item.find("div",{"class":"ticket-price"}).get_text() schedulelist.append(schedule) print(len(schedulelist)) # 遞歸 get_one(page=page+1, schedulelist=schedulelist) else: # 當不知足條件時返回內容,跳出遞歸 print("獲取完成") return return schedulelist class InsertSchedule(object): def __init__(self, conn): self.conn = conn def insert(self,schedulelist): cursor = self.conn.cursor() try: for schedule in schedulelist: sql = "insert schedule values(\"%s\",\"%s\",\"%s\",\"%s\",\"%s\")"%(schedule.get("startTime",None),schedule.get("startStation",None),schedule.get("arriveStation",None),schedule.get("busLevel",None),schedule.get("ticketPrice",None)) print(sql) cursor.execute(sql) except Exception as e: print(e) raise e finally: cursor.close() # 直接運行當前文件時執行的內容 if __name__== "__main__": conn = pymysql.connect(host="localhost",port=3306,user="root",password="qaz123456",db="demo",charset="utf8mb4") insertSchedule=InsertSchedule(conn) try: schedulelist=get_one() insertSchedule.insert(schedulelist); conn.commit() except Exception as e: conn.rollback() print("出現問題:"+str(e)) raise e finally: conn.close()
我定義了一個InsertSchedule類來進行數據庫操做,用到了一些關於事務提交和回滾的概念。實際上我這個簡單的demo是不須要這些東西的,可能寫在函數裏面會更簡單明瞭一些。可是鑑於我最近學習的內容,就直接生搬硬套的拿過來用的,這種用法在不少其餘的地方是很是有意思的。
另外,聽說用循環插入多條數據是很是損耗數據庫性能的事情,能夠先得到插入多行的sql語句,再cursor.execute(sql)執行。固然,這樣的修改加上拼接字符串讓我感受很是難作,故老老實實作一個笨蛋初學者,將就如今這樣用了。
還有一件很重要的事情,操做數據庫的數據鏈接對象connection和遊標對象cursor都應該及時關閉,以避免浪費資源。
最終結果:
過程十分綿長曲折,但最終結果讓人成功滿滿,清晰的數據呈如今眼前,彷彿打開了一扇新世界的大門。
參考資料: