PYTHON爬蟲入門&視頻網(wǎng)站BILIBILI用戶爬取爬蟲詳解
前言
Python使用版本:2.7
得到數(shù)據(jù)挖掘的課題后,我接觸到了Python,也發(fā)現(xiàn)了在同樣的命題下,使用的工具不同,方法不同味悄,即使是得到了同樣的結(jié)果,耗費(fèi)的人力時(shí)間成本也不盡相同塌鸯。
這引起了我對(duì)典型爬蟲方式的便捷程度(主要是程序員角度)的一點(diǎn)思考侍瑟。
典型?
就目前學(xué)習(xí)到的知識(shí)來(lái)看,不使用Scrapy等爬蟲框架的情況下涨颜,Python爬蟲可以說(shuō)是由以下幾個(gè)功能組合起來(lái)的:
模擬瀏覽器發(fā)送請(qǐng)求(GET/POST)
獲取方式分類
GET方法
范例采用requests擴(kuò)展庫(kù)的get函數(shù)
返回頁(yè)面內(nèi)容:
# -*- coding:utf-8 -*-
import requests
url = 'http://space.bilibili.com/218345/#!/'
response = requests.get(url).content
print response
有時(shí)GET方法也有除網(wǎng)址以外的傳入?yún)?shù):Query String Parameters
# -*- coding:utf-8 -*-
import requests
import datetime, time
def datetimeTrans(d):
currentMachTime = lambda: int(round(time.time() * 1000))
return currentMachTime()
url = 'http://search.bilibiili.com/all'
payload = {
'keyword': 'THE GIRL WITH THE'
}
result = requests.get(url, params = payload).content #inject query string parameters
print result
POST方法
范例使用requests.post()
POST方法常用于請(qǐng)求模擬登陸费韭,需傳入?yún)?shù)“Form Data”
# -*- coding:utf-8 -*-
import requests
import time, datetime
def datetimeTrans(d):
currentMachTime = lambda: int(round(time.time() * 1000))
return currentMachTime()
url = 'http://space.bilibili.com/ajax/member/GetInfo?mid=218345'
headers = {
#pass
}
payload = {
'mid': 218345
'_': datetimeTrans(datetime.datetime.now())
}
print url
response = requests.post(url, headers = headers, data = payload).content #inject form data
print response
GET&POST對(duì)比
GET相較于POST的優(yōu)勢(shì)
(引用知乎上羅志宇的回答)
- 請(qǐng)求中的URL可以被手動(dòng)輸入
- 請(qǐng)求中的URL可以被存在書簽里,或者歷史里庭瑰,或者快速撥號(hào)里面星持,或者分享給別人。
- 請(qǐng)求中的URL是可以被搜索引擎收錄的弹灭。
- 帶云壓縮的瀏覽器督暂,比如Opera mini/Turbo 2, 只有GET才能在服務(wù)器端被預(yù)取的。
- 請(qǐng)求中的URL可以被緩存穷吮。
POST相較于GET方法的特性
-POST不像GET的URL會(huì)顯示在瀏覽器歷史和WEB服務(wù)器日志中逻翁,相較更為安全
-在準(zhǔn)則中,不可重復(fù)的操作(例如創(chuàng)建條目或修改一條記錄)捡鱼,多是由POST請(qǐng)求完成的八回,因?yàn)镻OST不能被緩存,所以瀏覽器不會(huì)多次遞交驾诈。
(關(guān)于這條我們舉個(gè)小例子缠诅。假如你做了一個(gè)BLOG,并設(shè)計(jì)了一個(gè)URL來(lái)刪除你的BLOG上的所有帖子…那么畫面就會(huì)變得很美:一個(gè)搜索引擎的爬蟲爬過(guò)翘鸭,很快你就知道什么叫不作死就不會(huì)死了。)
可見戳葵,POST的GET的運(yùn)用是需要分場(chǎng)合的就乓。
結(jié)合網(wǎng)頁(yè)實(shí)例判別請(qǐng)求類型
由于項(xiàng)目是關(guān)于視頻網(wǎng)站bilibili的,網(wǎng)頁(yè)實(shí)例也來(lái)自bilibili拱烁。
(使用chrome瀏覽器自帶的檢查工具獲取network信息)
GET(myinfo)
需cookie和傳入?yún)?shù)
POST(getinfo)
Form Data為機(jī)器時(shí)間與用戶ID
元素定位及資源篩選
光是獲得了網(wǎng)頁(yè)內(nèi)容生蚁,不加以處理將占用大量的空間。所以在進(jìn)行存儲(chǔ)之前戏自,需要定位信息邦投,進(jìn)行信息的過(guò)濾。
這個(gè)時(shí)候就可以選用Xpath或者BeautifulSoup對(duì)網(wǎng)頁(yè)內(nèi)容(HTML/XML)內(nèi)容進(jìn)行分析擅笔。
Xpath
從dom網(wǎng)址尋找網(wǎng)址信息及標(biāo)題信息
# -*- coding:utf-8 -*-
from lxml import etree
dom = etree.HTML(all_text) #all_text is a string object
url_list = dom.Xpath('//table[@class="list"]/tr[@class="even" or "odd"]/td/span/a[1]/@href') #find links (@href)
title = dom.Xpath('//*[@id="content"]/div[3]/h1/text()') #find titles (text())
print title
print url_list
BeautifulSoup
BeatufulSoup實(shí)例可以通過(guò)標(biāo)簽等對(duì)象直接進(jìn)行結(jié)點(diǎn)資料的訪問(wèn)志衣,與數(shù)據(jù)儲(chǔ)存的對(duì)接口非常的好
prettify() 結(jié)構(gòu)簡(jiǎn)明化函數(shù),實(shí)例.標(biāo)簽可直接訪問(wèn)結(jié)點(diǎn)
# -*- coding:utf-8 -*-
import requests
from bs4 import BeautifulSoup as Soup
url = 'http://space.bilibili.com/218345/#!/'
response = requests.get(url).content
soup = Soup(response)
print soup.prettify()
print soup.title.string
print soup.a
print soup.h1
數(shù)據(jù)保存
- 不再贅述文件讀寫保存方式猛们,可查詢官方文檔或中文教程
MySQL數(shù)據(jù)庫(kù)
創(chuàng)建MySQL數(shù)據(jù)庫(kù)念脯,創(chuàng)建對(duì)應(yīng)數(shù)據(jù)的表
(指令在cmd或terminal的mysql界面中輸入)
CRATE TABLE bilibili;
USE bilibili;
CREATE TABLE `test` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`mid` varchar(11) DEFAULT NULL,
`name` varchar(45) DEFAULT NULL,
`sex` varchar(11) DEFAULT NULL,
`face` varchar(200) DEFAULT NULL,
`regtime` varchar(45) DEFAULT NULL,
`spacesta` int(11) DEFAULT NULL,
`birthday` varchar(45) DEFAULT NULL,
`place` varchar(45) DEFAULT NULL,
`attention` int(11) DEFAULT NULL,
`sign` varchar(300) DEFAULT NULL,
`attentions` text,
PRIMARY KEY (`id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8;
多線程爬取
map()使得并行代碼可以快速運(yùn)行,而支持map()函數(shù)并行的庫(kù)有multiprocessing.dummy和multiprocessing弯淘。
其中dummy庫(kù)的多進(jìn)程模塊采用的是線程(在Windows中绿店,進(jìn)行CPU分配是以線程為單位的,一個(gè)進(jìn)程可能由多個(gè)線程組成),這能夠使得數(shù)據(jù)輕松的在這兩個(gè)之間進(jìn)行前進(jìn)和回躍假勿,特別是對(duì)于探索性程序來(lái)說(shuō)十分有用借嗽。
# -*- coding:utf-8 -*-
from multiprocessing.dummy import Pool as ThreadPool
def getsource(url):
pass
pool = ThreadPool(1)
try:
results = pool.map(getsource, url)
except Exception:
pass
服務(wù)器代理
我選擇的阿里云服務(wù)器在進(jìn)行爬蟲代理爬取的時(shí)候,經(jīng)歷了三個(gè)環(huán)節(jié):
- 服務(wù)器配置(根據(jù)服務(wù)器官方文檔進(jìn)行)
- 爬蟲運(yùn)行環(huán)境設(shè)置(主要是Mysql及Python擴(kuò)展庫(kù)配置)
- 遠(yuǎn)程連接(windows自帶的mstsc進(jìn)程)
關(guān)于這些環(huán)節(jié)转培,網(wǎng)上都能找到相應(yīng)教程
非典型又是什么恶导?
到以上為止,一個(gè)基礎(chǔ)的Python爬蟲的常規(guī)構(gòu)造過(guò)程包括但絕不限于上文提到的內(nèi)容堡距。
還有正則表達(dá)式甲锡,去重判斷,或者是發(fā)送包的Form Data需要加密羽戒,構(gòu)造爬蟲的時(shí)候不僅可以選擇基于 HttpClient的爬蟲缤沦,還可以選擇內(nèi)置于瀏覽器引擎的爬蟲(PhantomJS,Selenium)等細(xì)節(jié)本文沒有提到易稠。但是由于文章的重點(diǎn)并不是典型的爬蟲寫法缸废,所以點(diǎn)到即止。
如果就以上提到深入下去驶社,可以說(shuō)日常生活中絕大部分能夠接觸到的網(wǎng)頁(yè)企量,都可以用上述提到的內(nèi)容進(jìn)行信息爬取。
但是如果我沒有接下來(lái)提到的這個(gè)項(xiàng)目亡电,記錄的角度可能就不是現(xiàn)在這樣的了届巩。
所謂非典型
首先要解決一個(gè)問(wèn)題:為什么我叫這個(gè)項(xiàng)目為非典型爬蟲。
這里主要介紹此類非典型爬蟲適用的網(wǎng)頁(yè)特性份乒,以及具體的代碼實(shí)現(xiàn)方法恕汇。
網(wǎng)頁(yè)對(duì)象特性
像是上文中提到過(guò)的通過(guò)模擬瀏覽器發(fā)送請(qǐng)求,在獲取信息后使用結(jié)點(diǎn)(BeautifulSoup或者Xpath及正則表達(dá)式)進(jìn)行所需數(shù)據(jù)段的訪問(wèn)或辖,可以說(shuō)是一種萬(wàn)能的獲取數(shù)據(jù)的方法瘾英。
但是就像下面關(guān)于BILIBILI用戶頁(yè)面的圖片中,network-Headers里顯示的一樣颂暇,用戶的信息是單獨(dú)發(fā)過(guò)來(lái)的json文件(Response Headers: Content-Type: application/json; charset=utf-8)缺谴。對(duì)于json文件就存在著更加便捷的訪問(wèn)方式。
在了解了網(wǎng)頁(yè)內(nèi)容的基礎(chǔ)上進(jìn)行這樣的操作耳鸯,就仿佛把獲取信息和信息篩選這兩個(gè)步驟合并了湿蛔,在處理數(shù)據(jù)上無(wú)疑輕松了不少。
Myinfo請(qǐng)求返回?cái)?shù)據(jù)preview:
Getinfo返回?cái)?shù)據(jù)preview:
從對(duì)比可以發(fā)現(xiàn)县爬,MyInfo中和GetInfo兩個(gè)請(qǐng)求返回的都是json內(nèi)容煌集,區(qū)別在于MyInfo獲取方式為GET,GetInfo獲取方式為POST捌省。
Json內(nèi)容的返回包使后續(xù)處理信息變得十分簡(jiǎn)便苫纤,只需要借用庫(kù)函數(shù)json.loads()就可以實(shí)現(xiàn)對(duì)返回?cái)?shù)據(jù)的提取,避免了分析網(wǎng)頁(yè)相對(duì)結(jié)點(diǎn)提取信息的步驟。
而如果對(duì)GetInfo網(wǎng)站返回的json數(shù)據(jù)進(jìn)行分析卷拘,可以發(fā)現(xiàn)返回包中已經(jīng)包含其關(guān)注人的ID的list喊废,對(duì)這個(gè)list的id進(jìn)行遍歷爬取,又可以獲得一系列活躍用戶的id栗弟,這樣應(yīng)該可以對(duì)特定大ip的用戶人群進(jìn)行用戶類型統(tǒng)計(jì)污筷。(有待完善)
返回包處理&判重
JS文件處理
jsDict = json.loads(jsoncontent)
if jsDict['status'] == True:
pass #use the dict to take out message in the jsoncontent
判重
- 數(shù)據(jù)直接存入數(shù)據(jù)庫(kù),并通過(guò)PRIMARY KEY(id)判斷是否已存在于數(shù)據(jù)庫(kù)乍赫。
- 通過(guò)for i in range()語(yǔ)句瓣蛀,程序員自己定制爬取用戶段
在這兩個(gè)判重條件下,去重變成了似乎不是那么重要的東西(僅限本項(xiàng)目)
數(shù)據(jù)庫(kù)重復(fù)信息顯示:
try:
pass #codes of pushing the data into a table
except MySQLdb.Error, e:
print "Mysql_Error %d: %s" % (e.args[0], e.args[1]) # if is the same, print error message
總結(jié)
Python寫就的典型爬蟲基本可以應(yīng)對(duì)常見的大部分網(wǎng)站雷厂,而此次介紹的非典型爬蟲案例惋增,則是根據(jù)個(gè)別網(wǎng)頁(yè)特性進(jìn)行針對(duì)方案寫就的,在獲取專門化的信息上提供了另外一種思路改鲫。
在想要得到的資料需要用一大串正則表達(dá)式才能夠匹配到之時(shí)诈皿,如果資料的獲取可以通過(guò)類似更加便捷的方式獲取,這樣的思路無(wú)疑會(huì)使代碼功能得到簡(jiǎn)化像棘。
但是鑒于并非所有的網(wǎng)站都用相同請(qǐng)求獲得用戶信息稽亏,真的必須使用正則的時(shí)候肯定也不少。在尋找快捷途徑的之前缕题,還是要求程序員要有扎實(shí)的基本功啊截歉。(sign)
參考項(xiàng)目/代碼
Requests: HTTP for Humans
Bilibili用戶信息
Bilibili模擬登陸
post 相比get 有很多優(yōu)點(diǎn),為什么現(xiàn)在的HTTP通信中大多數(shù)請(qǐng)求還是使用get
使用python+xpath獲取下載鏈接
Beautiful Soup Documentation
用map函數(shù)來(lái)完成Python并行任務(wù)的簡(jiǎn)單示例(對(duì)于multiprocessing.dummy的應(yīng)用)
Python: what are the differences between the threading and multiprocessing modules?
如何應(yīng)對(duì)網(wǎng)站反爬蟲策略烟零?如何高效地爬大量數(shù)據(jù)?