[TOC]
目標(biāo)
實(shí)現(xiàn)爬蟲的完整運(yùn)行啊掏,登陸,js解析衰猛,url去重迟蜜,通過中間件進(jìn)行功能擴(kuò)展,考慮驗(yàn)證碼破解啡省,頁面更新
js解析娜睛,可以考慮Pyv8,PythonWebKit卦睹,Selenium畦戒,PhantomJS,Ghost.py等
數(shù)據(jù)存儲(chǔ)结序,考慮用mongodb
去重兢交,考慮用BitVector
完整的爬取
scrapy里,先定義了starturl笼痹,然后parse函數(shù)會(huì)自動(dòng)去讀取這些url配喳,并做解析
可以通過request
Request(item['href'],callback=self.parescontent)
Scrapy uses Request and Response objects for crawling web sites.
Typically, Request objects are generated in the spiders and pass across the system until they reach the Downloader, which executes the request and returns a Response object which travels back to the spider that issued the request.
模擬登陸
def start_requests(self):
return [Request("https://www.zhihu.com/login", callback = self.post_login)] #重寫了爬蟲類的方法, 實(shí)現(xiàn)了自定義請(qǐng)求, 運(yùn)行成功后會(huì)調(diào)用callback回調(diào)函數(shù)
#FormRequeset
def post_login(self, response):
print 'Preparing login'
#下面這句話用于抓取請(qǐng)求網(wǎng)頁后返回網(wǎng)頁中的_xsrf字段的文字, 用于成功提交表單
xsrf = Selector(response).xpath('//input[@name="_xsrf"]/@value').extract()[0]
print xsrf
#FormRequeset.from_response是Scrapy提供的一個(gè)函數(shù), 用于post表單
#登陸成功后, 會(huì)調(diào)用after_login回調(diào)函數(shù)
return [FormRequest.from_response(response,
formdata = {
'_xsrf': xsrf,
'email': '123456',
'password': '123456'
},
callback = self.after_login
)]
配置代理頭
調(diào)用頁面解析時(shí)候可以指定header頭,requset的定義如下
class Request(object_ref):
def __init__(self, url, callback=None, method='GET', headers=None, body=None,
cookies=None, meta=None, encoding='utf-8', priority=0,
dont_filter=False, errback=None):
self._encoding = encoding # this one has to be set first
self.method = str(method).upper()
self._set_url(url)
self._set_body(body)
assert isinstance(priority, int), "Request priority not an integer: %r" % priority
self.priority = priority
assert callback or not errback, "Cannot use errback without a callback"
self.callback = callback
self.errback = errback
self.cookies = cookies or {}
self.headers = Headers(headers or {}, encoding=encoding)
self.dont_filter = dont_filter
self._meta = dict(meta) if meta else None
實(shí)例化Request的時(shí)候凳干,可以直接指定header
比如Request('http://www.baidu.com',callback=self.parescontent, method='GET', headers=sth, encoding='utf-8')
如果要配置多個(gè)header晴裹,并且自動(dòng)配置也很簡(jiǎn)單,方式如下
import random
headers = [headera,headerb,headerc]
Request('http://www.baidu.com',callback=self.parescontent, method='GET', headers=random.choice(headers) , encoding='utf-8')
布隆過濾器及改進(jìn)
布隆過濾器的思想 -- 不追求完美
在quora上救赐,有個(gè)問題問涧团,人們最常犯的錯(cuò)誤是哪些,其中一個(gè)就是追求完美经磅。
在it領(lǐng)域泌绣,為了讓系統(tǒng)完美化,比如之前涉及存儲(chǔ)系統(tǒng)预厌,去重時(shí)想達(dá)到完美的標(biāo)準(zhǔn)阿迈,花的代價(jià)是2小時(shí),如果稍加改動(dòng)轧叽,可以讓代價(jià)降低到1分鐘左右苗沧,只有本來的百分之一不到。
布隆過濾器的思想炭晒,也是如此待逞。
布隆過濾器的應(yīng)用 - 使用案例
squid
squid里的cache digests 用的是布隆過濾器
chrom
chrom里判斷惡意鏈接,也是用的布隆過濾器
hbase里也用了bloom filter
如圖
bloom filter在hbase里的用法比較有意思网严,它先判斷大小识樱,再做bf,這樣能讓查詢速度加快幾十倍
布隆過濾器的缺點(diǎn)和改進(jìn)
缺點(diǎn)
布隆過濾器的缺點(diǎn)是錯(cuò)判震束,就是說怜庸,不在里面的,可能判斷成在里面驴一,但是在里面的休雌,一定不會(huì)錯(cuò),而且肝断,無法刪除
改進(jìn)
改進(jìn)1 多bit
bloom filter其實(shí)就是bitmaq的改進(jìn)杈曲,bitmap是單個(gè)hash,而bf是多個(gè)hash胸懈,本質(zhì)上担扑,都是一個(gè)bit只能存有或者沒有兩種狀態(tài)
可以考慮用多bit記錄的方式,這種方法趣钱,就是本來每個(gè)bit不是0就是1涌献,現(xiàn)在可以記錄其它的,如果add一個(gè)元素首有,把對(duì)應(yīng)bit的數(shù)字加1即可
如果要del一個(gè)元素燕垃,對(duì)應(yīng)位置的數(shù)字減1即可
好處是枢劝,可以刪除元素
缺點(diǎn)是,可能會(huì)有位溢出卜壕,另外您旁,錯(cuò)判還是會(huì)發(fā)生,速度也慢了
改進(jìn)2 白名單
還有種改進(jìn)方式是對(duì)一些常見的url加到白名單里
這種改進(jìn)是不錯(cuò)的選擇轴捎,對(duì)于某些不考慮過濾的url鹤盒,可以先判斷一下,剩下的url錯(cuò)判就錯(cuò)判侦副,對(duì)結(jié)果影響是可以接受
布隆過濾器的細(xì)節(jié) - 算法的實(shí)現(xiàn)
下面用pybloom演示一下布隆過濾器的用法
from pybloom import BloomFilter
from pybloom import benchmarks
f = BloomFilter(capacity=100000, error_rate=0.1)
# [f.add(x) for x in range(102)]
[f.add(x) for x in range(1001)]
for x in range(1001, 100000000):
if x in f:
print x
可以看出侦锯,布隆過濾器,還是比較高效的一種數(shù)據(jù)結(jié)構(gòu)
存儲(chǔ)
先安裝monogdb秦驯,創(chuàng)建一個(gè)庫叫l(wèi)ei尺碰,再建個(gè)表,叫做questions
然后在setings.py中配置mongodb的ip汇竭,port葱蝗,db name,collection name這些
ITEM_PIPELINES = {
'tutorial.pipelines.MongoDBPipeline': 300,
}
MONGODB_SERVER = "192.168.100.110"
MONGODB_PORT = 27017
MONGODB_DB = "lei"
MONGODB_COLLECTION = "questions"
# DOWNLOAD_DELAY = 5 # 抓取的延遲
然后建一個(gè)pipelines.py细燎,這個(gè)pipelines是用來處理item的两曼,把item解析后存到mongodb里,代碼也很簡(jiǎn)單
import pymongo
from scrapy.conf import settings
from scrapy.exceptions import DropItem
from scrapy import log
class MongoDBPipeline(object):
def __init__(self):
connection = pymongo.MongoClient(
settings['MONGODB_SERVER'],
settings['MONGODB_PORT']
)
db = connection[settings['MONGODB_DB']]
self.collection = db[settings['MONGODB_COLLECTION']]
def process_item(self, item, spider):
for data in item:
if not data:
raise DropItem("Missing data!")
self.collection.update({'url': item['url']}, dict(item), upsert=True)
log.msg("Question added to MongoDB database!",level=log.DEBUG, spider=spider)
return item
執(zhí)行scrapy crawl 51job
玻驻,在robomon里即可看到爬取的結(jié)果
{
"_id" : ObjectId("57cfcebc2e45cfeb955b5483"),
"url" : "http://jobs.51job.com/nanjing-xwq/81462838.html?s=0",
"job_pay" : "6000-7999/月",
"company_type" : "民營公司",
"company_scale" : "50-150人",
"company_industry" : "影視/媒體/藝術(shù)/文化傳播",
"job_describe" : "現(xiàn)招一名完美的男孩他要坐立筆直悼凑,言行端正。他要行動(dòng)迅速璧瞬,不出聲響户辫。他可以在大街上吹口哨,但在該保持安靜的地方不吹口哨嗤锉。他看起來要精神愉快渔欢,對(duì)每個(gè)人都笑臉相迎,從不生氣瘟忱。他要禮貌待人奥额,尊重女人他愿意說一口純正的普通話,而不是家鄉(xiāng)話访诱。他在與女孩的相處中不緊張垫挨。他和自己的母親相處融洽,與她的關(guān)系最為親近触菜。他不虛偽九榔,也不假正經(jīng),而是健康,快樂哲泊,充滿活力剩蟀。",
"job_title" : "現(xiàn)招一名完美的男孩 投資理財(cái)顧問助理總助"
}
遇到的問題
beautifulsoup解析頁面,結(jié)果缺失的問題
在解析http://search.51job.com/list/070200,070201,0000,00,9,99,%2B,2,1.html
時(shí)攻旦,這個(gè)頁面的url一直解析不出來所有的url
一直以為是我代碼寫的有問題喻旷,坑了一上午,發(fā)現(xiàn)是解析器的問題 fire :-(
如果用bs4牢屋,載入時(shí)候html5不行,就換html槽袄,soup = BeautifulSoup(temp, "html.parser")
運(yùn)行時(shí)報(bào)錯(cuò)
提示 AttributeError: 'list' object has no attribute 'iteritems'
原因是在settings文件里烙无,配置item pipelines的時(shí)候,沒有指定順序遍尺,正確的方式如下:
ITEM_PIPELINES = {
'myproject.pipelines.PricePipeline': 300,
'myproject.pipelines.JsonWriterPipeline': 800,
}
items是根據(jù)數(shù)字大小截酷,從低到高通過pipelines,數(shù)字的范圍是0-1000