作者 | 周蘿蔔
責編 | 郭芮
本文章精選了五個爬蟲實例,希望能夠給想要入門 Python 爬蟲的小夥伴兒們一些幫助。
網易精選評價爬取
首先來看一個網易精選網站的爬蟲例子,可以爬取評價的商品很多,這裡選擇「iPhone」關鍵字為例子,因為此類商品種類、樣式、顏色等比較多,利於後面的數據分析。
分析網頁
評論分析
進入到網易精選官網,搜索「iPhone」後,先隨便點進一個商品。
在商品頁面,打開 Chrome 的控制臺,切換至 Network 頁,再把商品頁面切換到評價標籤下,選擇一個評論文字,如「手機套很薄,裸機的手感」,在 Network 中搜索。
可以發現,評論文字是通過 listByItemByTag.json 傳遞過來的,點擊進入該請求,並拷貝出該請求的 URL:
將該 URL 放入 Postman 中,逐個嘗試 url query params,最後能夠發現,只需保留 itemId 和 page 兩個請求參數即可。
請求返回的是一個 JSON 格式的數據,下面就是分析該 JSON 數據了。
不難發現,所有的評論數據都存儲在 commentList 中,我們只需保存該數據即可。
下面就是如何獲取 itemId 的信息了,這個是產品的 ID,我們回到網易精選首頁,繼續分析。
產品 ID 獲取
當我們在搜索框中輸入關鍵字進行搜索的時候,同樣能夠發現在 Network 中有很多請求,此時可以觀察各個請求,通過請求文件的名稱(此處需要一些經驗,守規矩的程式設計師都不會亂起名字),我們可以定位到搜索時展示搜索結果的請求。
搜索一般都是 search,所以我們就鎖定了這個 search.json 的請求。同樣把請求 URL 拷貝到 Postman 中,逐個驗證傳參,最後保留 page 和 keyword 兩個參數即可。
該請求返回的數據較多,還是需要耐心的分析數據,也能夠發現,在 result->data->directly->searcherResult->result 下面的 id 值,即為我們要獲取的產品 ID。
以上,我們基本完成了前期的分析工作,下面開始代碼的編寫。
編寫代碼
獲取產品 ID
Pythondefsearch_keyword(keyword): uri = 'https://you.163.com/xhr/search/search.json' query = {"keyword": keyword,"page": 1 }try: res = requests.get(uri, params=query).json() result = res['data']['directly']['searcherResult']['result'] product_id = []for r in result: product_id.append(r['id'])return product_idexcept:raise
我這裡是獲取了 page 為 1 的產品 ID,下面就是通過產品 ID 來獲取不同產品下的評論信息。
通過前面的分析,我們可以知道,評論信息都是如下形式的,對這種形式的信息,我們可以很方便地存儲進入 MongoDB,然後再慢慢分析數據裡的內容。
{"skuInfo": ["機型:iphone X","顏色:透明白" ],"frontUserName": "c****x","frontUserAvatar": "https://yanxuan.nosdn.127.net/c230f2c2a6e7223810755518ce5cd62f","content": "手機套很薄,裸機的手感,矽膠的摸著好舒服,看著屏幕都變大了。顏值很高。","createTime": 1554982110981,"picList": ["https://yanxuan.nosdn.127.net/749cfcb4877be8e7fce449900d763731.jpg","https://yanxuan.nosdn.127.net/6744d04efcd7509556b710bc0d9fa6b0.jpg" ],"commentReplyVO": null,"memberLevel": 4,"appendCommentVO": null,"star": 5,"itemId": 3444035 }
對於 MongoDB,我們既可以自己搭建,也可以使用網上免費的服務。在這裡我介紹一個免費的 MongoDB 服務網站:mlab,使用很簡單,就不過多介紹使用過程了。
資料庫有了,下面就是把數據保存進去了。
defdetails(product_id): url = 'https://you.163.com/xhr/comment/listByItemByTag.json'try: C_list = []for i in range(1, 100): query = {"itemId": product_id,"page": i, } res = requests.get(url, params=query).json()ifnot res['data']['commentList']:break print("爬取第 %s 頁評論" % i) commentList = res['data']['commentList'] C_list.append(commentList) time.sleep(1)# save to mongoDBtry: mongo_collection.insert_many(commentList)except:continuereturn C_listexcept:raise
在這裡,details 這個函數還返回了評論的列表,如果你還有其他的邏輯需要對評論處理,可以接收這個函數的返回值,然後處理該列表。
最後爬取完成之後,總共是七千多條數據,下面就可以根據個人需要做一些分析了。
完整代碼地址:https://github.com/zhouwei713/dataanalysis/tree/master/you163spider。
爬取的數據 MongoDB 連結:
conn = MongoClient("mongodb://%s:%s@ds149974.mlab.com:49974/you163" % ('you163', 'you163'))db = conn.you163mongo_collection = db.iPhone
Boss 直聘網站爬取
其實分析的步驟都是類似的,無外乎是先分析網站信息,頁面組成,然後編寫代碼爬取數據,最後再保存數據。
頁面分析
我們選取一個崗位來分析,比如:Python。
在 Boss 直聘的官網上搜索 Python,可以看到瀏覽器的 URL 變為如下:
把該地址複製到 Postman 嘗試訪問,發現無法得到正確的返回:
此時,再次回到瀏覽器,查看該請求下面的 headers,可以看到其中有一個 cookie,是很長的一串字符串,我們拷貝這個 cookie 到 Postman 中,再次請求:
成功了,看來 Boss 直聘網也只是做了簡單的 cookies 驗證。
BeautifulSoup 使用
下面就是解析 HTML 數據了,我比較習慣用 BeautifulSoup 這個庫來解析。
from bs4 import BeautifulSoupimport requestsurl = 'https://www.zhipin.com/job_detail/?query=python&city=101010100'res = requests.get(url, headers=header).textprint(res)content = BeautifulSoup(res, "html.parser")ul = content.find_all('ul')print(ul[12])
可以使用 BeautifulSoup 的 find 函數來查找 HTML 的元素標籤,個人覺得還是挺方便的。
編寫代碼
我們通過分析 HTML 網頁可以知道,所有的工作信息都是保存在 ul 這個標籤中的,我們可以通過上面的代碼拿到頁面中所有的 ul 標籤,find_all 返回的是一個列表,然後再查看,工作具體位於第幾個 ul 中,這樣就拿到具體的工作信息了。
確定需要抓取的數據
如圖中所示,我們需要抓取紅框中的信息,主要分為四部分。
Python:可以得到該 job 具體頁面地址10-15K:每個 job 的薪資柯萊特集團:招聘公司名稱北京 朝陽區 望京|3-5年|學歷不限:該 job 的詳情信息對於前三個信息,還是比較好抓取的。
job_details_uri = job.find('h3', attrs={'class': 'name'}).find('a')['href']job_company = job.find('div', attrs={'class': 'company-text'}).find('h3', attrs={'class': 'name'}).find('a').textjob_salary = job.find('h3', attrs={'class': 'name'}).find('span', attrs={'class': 'red'}).text
對於 job 的詳情信息,需要用到正則表達式來分割字符串,我們先來看下該部分的原始形式:
又可以把該部分切分成三塊,site、year 和 edu。可以使用正則的 group 特性,幫助我們完成切分,最後我寫的正則如下:
rege = r'<p>([\u4e00-\u9fa5 ]+)<em></em>([\d+-年]+|[\u4e00-\u9fa5]+)<em></em>([\u4e00-\u9fa5]+)'
正則表達式的具體寫法這裡就不說了,不熟悉的可以自行查找下。
下面把這部分代碼整合到一起:
for job in jobs: job_dict = {} job_details_uri = job.find('h3', attrs={'class': 'name'}).find('a')['href'] job_company = job.find('div', attrs={'class': 'company-text'}).find('h3', attrs={'class': 'name'}).find('a').text job_salary = job.find('h3', attrs={'class': 'name'}).find('span', attrs={'class': 'red'}).text job_details = str(job.find('p')) print(job_details) job_rege = re.match(rege, job_details) job_dict['name'] = job_company job_dict['uri'] = job_details_uri job_dict['salary'] = job_salary job_dict['site'] = job_rege.group(1) job_dict['year'] = job_rege.group(2) job_dict['edu'] = job_rege.group(3) job_list.append(job_dict)print(job_list)
由於我們最後還是得到了一個列表,裡面包含字典,同樣可以快捷的保存到 MongoDB 中。
抓取多個頁面
通過查看 Boss 網站的下一頁源碼可得到翻頁 URL 的規律:
https://www.zhipin.com/c101010100/?query=python&page=
c101010100:是城市代碼,在我們這裡就代表北京query:也很明顯,就是我們的搜索關鍵字page:頁數
於是我們最後整合代碼如下:
defjobs(page):for i in range(1, page + 1): job_list = []try: print("正在抓取第 %s 頁數據" % i) uri = '/c101010100/?query=python&page=%s' % i res = requests.get(config.url + uri, headers=header).text content = BeautifulSoup(res, "html.parser") ul = content.find_all('ul') jobs = ul[12].find_all("li") ... print(job_list)# save to mongoDBtry: mongo_collection.insert_many(job_list)except:continue time.sleep(1)except:continue
因為我上面的正在表達式並不能匹配所有的情況,所以使用 try...except 來忽略了其他不規則的情況。
崗位詳情抓取
job 詳情抓取完畢之後,開始抓取崗位詳情,就是每個 job 的具體要求,畢竟知己知彼,百戰不殆。
我們可以從 URI 中獲得每個工作的詳情頁面地址,然後再拼接到 Boss 的主 URL 上:
https://www.zhipin.com/job_detail/a8920821a7487a901HJ43tm7EFY~.html
再來看下工作詳情頁面,所有的任職描述都在如下的 div 標籤中:
沒有什麼特殊的,直接用 BeautifulSoup 解析即可。
job_conn = MongoClient("mongodb://%s:%s@ds151612.mlab.com:51612/boss" % ('boss', 'boss123'))job_db = job_conn.bossjob_collection = job_db.bossdetails_collection = job_db.job_detailsdefrun_main(): jobs = job_collection.find()for job in jobs: print('獲得工作的uri ', job['uri']) get_details(job) time.sleep(1)defget_details(items): base_url = config.url url = base_url + items['uri'] company_name = items['name']try: res = requests.get(url, headers=header).text content = BeautifulSoup(res, "html.parser") text = content.find('div', attrs={'class': 'text'}).text.strip() result = {'name': company_name, 'details': text} details_collection.insert_one(result)except:raiseif __name__ == '__main__': run_main()
注意下這裡的 MongoDB 配置,是同一個 db 下的不同 collection。這樣,也就完成了直聘網站相關崗位的數據爬取。
完整代碼地址:https://github.com/zhouwei713/dataanalysis/tree/master/bossspider。
爬取的數據 MongoDB 連結:
job_conn = MongoClient("mongodb://%s:%s@ds151612.mlab.com:51612/boss" % ('boss', 'boss123'))job_db = job_conn.bossjob_collection = job_db.bossdetails_collection = job_db.job_details
微博爬取
這裡的微博爬蟲,我主要實現的是輸入你關心的某個大 V 的微博名稱,以及某條微博的相關內容片段,即可自動爬取相關該大 V 一段時間內發布的微博信息和對應微博的評論信息。
Cookie 獲取
與上面的 Boss 直聘網站類似,爬取微博也需要獲取響應的 cookie。
用瀏覽器打開微博頁面,拷貝出對應的 Cookie,保存到本地。
微博搜索
既然是某位大 V,這裡就肯定涉及到了搜索的事情,我們可以先來嘗試下微博自帶的搜索,地址如下:
s.weibo.com/user?q=林志玲
同樣是先放到 Postman 裡請求下,看看能不能直接訪問:
是可以的,這就省去了我們很多的麻煩。下面就是來分析並解析響應消息,拿到對我們有用的數據。
經過觀察可知,這個接口返回的數據中,有一個 UID 信息,是每個微博用戶的唯一 ID,我們可以拿過來留作後面使用。
至於要如何定位到這個 UID,我也已經在圖中做了標註,相信你只要簡單分析下就能明白。
defget_uid(name):try: url = 'https://s.weibo.com/user?q=%s' % name res = requests.get(url).text content = BeautifulSoup(res, 'html.parser') user = content.find('div', attrs={'class': 'card card-user-b s-pg16 s-brt1'}) user_info = user.find('div', attrs={'class': 'info'}).find('div') href_list = user_info.find_all('a')if len(href_list) == 3: title = href_list[1].get('title')if title == '微博個人認證': uid = href_list[2].get('uid')return uidelif title == '微博會員': uid = href_list[2].get('uid')return uidelse: print("There are something wrong")returnFalseexcept:raise
還是通過 BeautifulSoup 來定位獲取元素,最後返回 UID 信息。
M 站的利用
M 站一般是指手機網頁端的頁面,也就是為了適配 mobile 移動端而製作的頁面。一般的網站都是在原網址前面加「m.」來作為自己 M 站的地址,比如:m.baidu.com 就是百度的 M 站。
我們來打開微博的 M 站,再進入到林志玲的微博頁面看看 Network 中的請求,有沒有什麼驚喜呢?
我們首先發現了這樣一個 URL:
https://m.weibo.cn/api/container/getIndex?uid=1312412824&luicode=10000011&lfid=100103type%3D1%26q%3D%E6%9E%97%E5%BF%97%E7%8E%B2&containerid=1005051312412824
接著繼續拖動網頁,發現 Network 中又有類似的 URL:
https://m.weibo.cn/api/container/getIndex?uid=1312412824&luicode=10000011&lfid=100103type%3D1%26q%3D%E6%9E%97%E5%BF%97%E7%8E%B2&containerid=1076031312412824
URL 類似,但是第一個返回的數據是用戶信息,而第二個返回的則是用戶的微博信息,顯然第二個 URL 是我們需要的。同樣道理,把第二個 URL 放到 Postman 中,看看哪些參數是可以省略的。
最後我們發現,只要傳入正確的 containerid 信息,就能夠返回對應的微博信息,可是 containerid 信息又從哪裡來呢?我們剛剛獲得了一個 UID 信息,現在來嘗試下能不能通過這個 UID 來獲取到 containerid 信息。
這裡就又需要一些經驗了,我可以不停的嘗試給接口「m.weibo.cn/api/container/getIndex」添加不同的參數,看看它會返回些什麼信息,比如常見的參數名稱 type、id、value、name 等。最終,在我不懈的努力下,發現 type 和 value 的組合是成功的,可以拿到對應的 containerid 信息。
這個地方真的不有任何捷徑了,只能靠嘗試和經驗。
現在就可以編寫代碼,獲取對應的 containerid 了(如果你細心的話,還可以看到這個接口還返回了很多有意思的信息,可以自己嘗試著抓取)。
defget_userinfo(uid):try: url = 'https://m.weibo.cn/api/container/getIndex?type=uid&value=%s' % uid res = requests.get(url).json() containerid = res['data']['tabsInfo']['tabs'][1]['containerid'] mblog_counts = res['data']['userInfo']['statuses_count'] followers_count = res['data']['userInfo']['followers_count'] userinfo = {"containerid": containerid,"mblog_counts": mblog_counts,"followers_count": followers_count }return userinfoexcept:raise
代碼裡都是基本操作,不過多解釋了。
拿到 containerid 信息之後,我們就可以使用上面第二個 URL 來獲取微博信息了,這裡還是同樣的問題——分頁。怎麼處理分頁呢,繼續改造這個 getIndex 接口,繼續嘗試傳遞不同的參數給它。
這次給它傳遞 containerid 和 page 信息,就可以完成分頁請求了。
傳遞的 page 為 3 時,其實是獲取當前新浪微博的第 4 頁數據,後面我們就可以用這個 URL 來獲取微博信息了。
該接口返回的是 JSON 數據,解析起來就比較方便了。
微博信息就保存在 res['data']['cards'] 下面,有評論、轉發、點讚數量等信息。於是我們解析該 JSON 數據的函數就有了:
defget_blog_info(cards, i, name, page): blog_dict = {}if cards[i]['card_type'] == 9: scheme = cards[i]['scheme'] # 微博地址 mblog = cards[i]['mblog'] mblog_text = mblog['text'] create_time = mblog['created_at'] mblog_id = mblog['id'] reposts_count = mblog['reposts_count'] # 轉發數量 comments_count = mblog['comments_count'] # 評論數量 attitudes_count = mblog['attitudes_count'] # 點讚數量with open(name, 'a', encoding='utf-8') as f: f.write("----第" + str(page) + "頁,第" + str(i + 1) + "條微博----" + "\n") f.write("微博地址:" + str(scheme) + "\n" + "發布時間:" + str(create_time) + "\n" + "微博內容:" + mblog_text + "\n" + "點讚數:" + str(attitudes_count) + "\n" + "評論數:" + str(comments_count) + "\n" + "轉發數:" + str(reposts_count) + "\n") blog_dict['mblog_id'] = mblog_id blog_dict['mblog_text'] = mblog_text blog_dict['create_time'] = create_timereturn blog_dictelse: print("沒有任何微博哦")returnFalse
函數參數:
第一個參數,接受的值為 res['data']['cards'] 的返回值,是一個字典類型數據;第二個參數,是外層調用函數的循環計數器;第三個參數,是要爬取的大 V 名稱;第四個參數,是正在爬取的頁碼。最後函數返回一個字典。
搜索微博信息
我們還要實現通過微博的一些欄位,來定位到某個微博,從而抓取該微博下的評論的功能。
再定義一個函數,調用上面的 get_blog_info 函數,從其返回的字典中拿到對應的微博信息,再和需要比對的我們輸入的微博欄位做比較,如果包含,那麼就說明找到我們要的微博啦。
defget_blog_by_text(containerid, blog_text, name): blog_list = [] page = 1whileTrue:try: url = 'https://m.weibo.cn/api/container/getIndex?containerid=%s&page=%s' % (containerid, page) res_code = requests.get(url).status_codeif res_code == 418: print("訪問太頻繁,過會再試試吧")returnFalse res = requests.get(url).json() cards = res['data']['cards']if len(cards) > 0:for i in range(len(cards)): print("-----正在爬取第" + str(page) + "頁,第" + str(i+1) + "條微博------") blog_dict = get_blog_info(cards, i, name, page) blog_list.append(blog_dict)if blog_list isFalse:break mblog_text = blog_dict['mblog_text'] create_time = blog_dict['create_time']if blog_text in mblog_text: print("找到相關微博")return blog_dict['mblog_id']elif checkTime(create_time, config.day) isFalse: print("沒有找到相關微博")return blog_list page += 1 time.sleep(config.sleep_time)else: print("沒有任何微博哦")breakexcept:pass
這裡調用了一個工具函數 checkTime 和一個配置文件 config。
checkTime 函數定義如下:
defcheckTime(inputtime, day):try: intime = datetime.datetime.strptime("2019-" + inputtime, '%Y-%m-%d')except:return"時間轉換失敗" now = datetime.datetime.now() n_days = now - intime days = n_days.daysif days < day:returnTrueelse:returnFalse
定義這個函數的目的是為了限制搜索時間,比如對於 90 天以前的微博,就不再搜索了,也是提高效率。
而 config 配置文件裡,則定義了一個配置項 day,來控制可以搜索的時間範圍:
day = 90# 最久抓取的微博時間,60即為只抓取兩個月前到現在的微博sleep_time = 5# 延遲時間,建議配置5-10s
獲取評論信息
對於微博評論信息的獲取,要簡單很多。
我們進入某一個微博頁面,進入到評論區:
https://weibo.com/1312412824/HxFY84Gqb?filter=hot&root_comment_id=0&type=comment#_rnd1567155548217
從 Network 中可以拿到一個請求 URL:
https://weibo.com/aj/v6/comment/big?ajwvr=6&id=4380261561116383&from=singleWeiBo&__rnd=1567155729639
同樣使用 Postman 進行 URL 精簡和分頁處理,可以得到最後的 URL 為:
https://weibo.com/aj/v6/comment/big?ajwvr=6&id=%s&page=%s
id 就是要抓取評論的微博對應的 id,我們已經在上面的接口中拿到了;page 就是請求頁數。獲取評論及保存數據代碼:
defget_comment(self, mblog_id, page): comment = []for i in range(0, page): print("-----正在爬取第" + str(i) + "頁評論") url = 'https://weibo.com/aj/v6/comment/big?ajwvr=6&id=%s&page=%s' % (mblog_id, i) req = requests.get(url, headers=self.headers).text html = json.loads(req)['data']['html'] content = BeautifulSoup(html, "html.parser") comment_text = content.find_all('div', attrs={'class': 'WB_text'})for c in comment_text: _text = c.text.split(":")[1] comment.append(_text) time.sleep(config.sleep_time)return commentdefdownload_comment(self, comment): comment_pd = pd.DataFrame(columns=['comment'], data=comment) timestamp = str(int(time.time())) comment_pd.to_csv(timestamp + 'comment.csv', encoding='utf-8')
定義運行函數
最後,我們開始定義運行函數,把需要用戶輸入的相關信息都從運行函數中獲取並傳遞給後面的邏輯函數中。
from weibo_spider import WeiBofrom config import headersdefmain(name, spider_type, text, page, iscomment, comment_page): print("開始...") weibo = WeiBo(name, headers) ...if __name__ == '__main__': target_name = input("type the name: ") spider_type = input("type spider type(Text or Page): ") text = "你好" page_count = 10 iscomment = "No" comment_page_count = 100while spider_type notin ("Text", "text", "Page", "page"): spider_type = input("type spider type(Text or Page): ") ...
通過 input 函數接受用戶輸入信息,再判斷程序執行。
爬蟲類與工具集
最後再來看下程序中的 WeiBo 爬蟲類的定義:
classWeiBo(object):def__init__(self, name, headers): self.name = name self.headers = headersdefget_uid(self):# 獲取用戶的 UID ...defget_userinfo(self, uid):# 獲取用戶信息,包括 containerid ...defget_blog_by_page(self, containerid, page, name):# 獲取 page 頁的微博信息 ...defget_blog_by_text(self, containerid, blog_text, name):# 一個簡單的搜索功能,根據輸入的內容查找對應的微博 ...defget_comment(self, mblog_id, page):# 與上個函數配合使用,用於獲取某個微博的評論 ...defdownload_comment(self, comment):# 下載評論 ...
在類的初始化函數中,傳入需要爬取的大 V 名稱和我們準備好的 headers(cookie),然後把上面寫好的函數寫道該類下,後面該類的實例 weibo 就能夠調用這些函數了。
對於工具集,就是抽象出來的一些邏輯處理:
import datetimefrom config import daydefcheckTime(inputtime, day): ...defget_blog_info(cards, i, name, page): ...
最終程序運行示例:
完整代碼地址:https://github.com/zhouwei713/weibo_spider。
女神大會數據爬取
繼續爬取懂球帝的女神大會數據。
頁面 ID 信息獲取
不過這裡有一個問題,以前的懂球帝是帶有搜索功能的,所以我們能從搜索功能中找到一個用於搜索的 API,但是現在該功能不見了,所以這裡已經沒有辦法展示如何拿到搜索 API 的過程了。
但是搜索 API 我們還是可以使用的:
http://api.dongqiudi.com/search?keywords=&type=all&page=
我們可以通過給 keyword 傳入「女神大會」關鍵字,來獲取到女神大會相關的信息:
id 是對應的每個網頁的 id;thumb 是女神的封面圖片;url 對應的也是女神所在頁面的地址信息。於是,我們可以通過輸入不同的 page 數值,獲取到所有的 JSON 信息,並解析 JSON,保存我們需要的數據:
defget_list(page): nvshen_id_list = [] nvshen_id_picture = []for i in range(1, page): print("獲取第" + str(i) + "頁數據") url = 'http://api.dongqiudi.com/search?keywords=%E5%A5%B3%E7%A5%9E%E5%A4%A7%E4%BC%9A&type=all&page=' + str(i) html = requests.get(url=url).text news = json.loads(html)['news']if len(news) == 0: print("沒有更多啦")break nvshen_id = [k['id'] for k in news] nvshen_id_list = nvshen_id_list + nvshen_id nvshen_id_picture = nvshen_id_picture + [{k['id']: k['thumb']} for k in news] time.sleep(1)return nvshen_id_list, nvshen_id_picture
下載 HTML 頁面
我們把上面 API 拿到的 url 欄位放到懂球帝的主地址下,拼接成如下 url:
http://www.dongqiudi.com/news/1193890
打開該 URL,發現確實是對應的詳情頁面:
我們觀察該頁面,發現球迷朋友們的評分都是展示在頁面上的,且是上一期女神的評分,於是我決定把所有的 HTML 下載到本地,然後再慢慢解析頁面,抽取數據:
defdownload_page(nvshen_id_list):for i in nvshen_id_list: print("正在下載ID為" + i + "的HTML網頁") url = 'https://www.dongqiudi.com/news/%s' % i download = DownloadPage() html = download.getHtml(url) download.saveHtml(i, html) time.sleep(2)classDownloadPage(object):defgetHtml(self, url): html = requests.get(url=url, cookies=config.session, headers=config.header).contentreturn htmldefsaveHtml(self, file_name, file_content):with open('html_page/' + file_name + '.html', 'wb') as f: f.write(file_content)
download_page 函數接收一個列表(就是上一個函數的返回值 nvshen_id_list),通過 requests 庫來請求頁面並保存至本地。同時為了方式請求過於頻繁,做了 2 秒的延時等待設置。
這裡還要注意一點的是,這裡是設置了 cookies 和 headers 的,否則是無法拿到正常 HTML 數據的,headers 還是從瀏覽器中手動拷貝出來:
session = {'dqduid': 'yours'}header = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win32; x32; rv:54.0) Gecko/20100101 Firefox/54.0','Connection': 'keep-alive'}
解析 HTML
對於 HTML 的解析,我依舊使用 BeautifulSoup。主要需要獲取 score 信息,但是由於好多 HTML 頁面寫的都不是很規則,故而這裡耗費了比較多的時間。
對於得分情況,我是這麼寫的:
content.find_all('span', attrs={'style': "color:#ff0000"})
但是有些頁面的規則不同,所以會存在無法解析一部分 HTML 文件,對於無法處理的文件,由於也不多,就手工處理了。
當然,感覺這裡使用正則應該會好很多。代碼過長,為了不影響閱讀,就不貼出來了,可以到 GitHub 上查看完整代碼。
defdeal_loaclfile(nvshen_id_picture): files = os.listdir('html_page/') nvshen_list = [] special_page = []for f in files: ...return nvshen_list, special_pagedefget_picture(c, t_list, n_id_p): nvshen_l = [] tmp_prev_id = c.find_all('a', attrs={"target": "_self"})for j in tmp_prev_id: ...
有一部分 HTML 代碼不是很規範,專門提出了這一部分:
w_list = ['吳宣儀', '30萬', '826965', '68', '825847','https://img1.dongqiudi.com/fastdfs3/M00/74/54/180x135/crop/-/ChOxM1vIPpOAZT8AAAHza_WMyRk175.png'] g_list = ['關之琳', '20萬', '813611', '88', '812559','https://img1.dongqiudi.com/fastdfs3/M00/6B/94/180x135/crop/-/ChOxM1u1gx2AZ7qmAABi3gRdHS8715.jpg'] t_list = ['佟麗婭', '22萬', '797779', '93', '795697','https://img1.dongqiudi.com/fastdfs3/M00/60/A7/180x135/crop/-/ChOxM1ufUh2AJdR0AABXtcU22fg956.jpg'] y_list = ['楊丞琳', '7萬', '1173681', '45', '1168209','https://img1.qunliao.info/fastdfs4/M00/CA/F7/ChMf8F0pTOKAaefqAA5nOMM0LK0171.jpg']
保存數據
對於數據的保存,直接保存在了本地的 CSV 中,代碼就不貼了。
完整代碼地址:https://github.com/zhouwei713/data_analysis/tree/master/nvshendahui。
展示網站地址:https://nvshen.luobodazahui.top/。
豆瓣海報爬取
最後,我們再來看看豆瓣的爬取。其實豆瓣網站還是蠻友好的,沒有什麼反爬機制,所以我們也需要投桃報李,注意爬蟲的速率,不要訪問過快。
我這裡就以女神王祖賢的海報來作為例子。
翻頁分析
在豆瓣電影中搜索「王祖賢」,進入王祖賢主頁後,點擊全部影人圖片,進入到影人圖片頁面。
在該頁面點擊下一頁,可以看到瀏覽器的 URL 變化如下:
https://movie.douban.com/celebrity/1166896/photos/?type=C&start=30&sortby=like&size=a&subtype=a
繼續使用 Postman 來分析 URL,可以很輕鬆的得知,start 就是類似於 page 的頁數控制參數,而且步長為 30,即第一頁是 start = 0,第二頁為 start = 30,第三頁為 start = 60,以此類推。
詳情頁分析
使用 Network 來查看頁面上的圖片信息:
這裡我們得到了兩個信息:
a 標籤中的連結可以得到每張圖片的評論信息;img 標籤中的連結可以用來保存女神的海報。對於這兩個信息 url,可以分別返回:
defget_posters(): comment_url_list = [] picture_list = []for i in range(0, 40000, 30): url = 'https://movie.douban.com/celebrity/1166896/photos/?type=C&start=%s&sortby=like&size=a&subtype=a' % str(i) req = requests.get(url).text content = BeautifulSoup(req, "html.parser") chekc_point = content.find('span', attrs={'class': 'next'}).find('a')if chekc_point != None: data = content.find_all('div', attrs={'class': 'cover'})for k in data: ulist = k.find('a')['href'] plist = k.find('img')['src'] comment_url_list.append(ulist) picture_list.append(plist)else:breakreturn comment_url_list, picture_list
之後,就可以下載海報了。
評論獲取
然後我們手動跳轉到每周海報的詳情頁面,繼續查看評論信息。
通過 BeautifulSoup 可以很容易地獲得評論信息,然後保存到 MongoDB 中。
defget_comment(comment_l): client = pymongo.MongoClient('mongodb://douban:douban1989@ds149744.mlab.com:49744/douban') db = client.douban mongo_collection = db.comment comment_list = [] comment = [] print("Save to MongoDB")for i in comment_l: response = requests.get(i).text content = BeautifulSoup(response, "html.parser") tmp_list = content.find_all('div', attrs={'class': 'comment-item'}) comment_list = comment_list + tmp_listfor k in comment_list: tmp_comment = k.find('p').text mongo_collection.insert_one({'comment': tmp_comment}) comment.append(tmp_comment) print("Save Finish!")
完整代碼地址:https://github.com/zhouwei713/douban/tree/master/wangzuxian_poster。
爬取的數據 MongoDB 連結:
client = pymongo.MongoClient('mongodb://douban:douban1989@ds149744.mlab.com:49744/douban')db = client.doubanmongo_collection = db.comment
總結
本文章分享了五個爬蟲的實戰例子,由於為了照顧篇幅,不至於過長,所以省去了一些繁瑣重複的代碼解釋。當然,代碼部分難免會有些錯誤、不規範的地方,還請包涵。
在文章中,大部分代碼都是僅僅給出了一些思路和主要框架,還希望勵志於學習爬蟲的你,先好好思考爬蟲思路,手動自行敲一遍代碼,這樣才能達到最好的學習效果。
聲明:本文為作者原創投稿,未經允許請勿轉載。