網(wǎng)絡(luò)文學(xué)隨互聯(lián)網(wǎng)的崛起而崛起查牌,在時(shí)間日益碎片化的今天对碌,網(wǎng)絡(luò)文學(xué)以其方便快捷的特點(diǎn)適應(yīng)了人們的娛樂性需求荆虱,因而也快速成長一個(gè)巨大的市場。娛樂是人們的根本性需求朽们,文化是這個(gè)過程中的附屬品怀读。要想知道人們關(guān)心的是什么?什么又在興起骑脱,哪些題材又在逐漸沒落菜枷,我們需要從整體去分析。
因此這個(gè)爬蟲也就應(yīng)運(yùn)而生叁丧,我們選取了目前國內(nèi)最大的小說平臺——起點(diǎn)網(wǎng)作為數(shù)據(jù)來源啤誊。本爬蟲主要是為了爬取起點(diǎn)小說的基本信息(題目、作者歹袁、簡介等)坷衍,在寫爬蟲的過程中寝优,我也碰到了一些有趣的反爬機(jī)制条舔,這些小障礙我也會在本文中一并記錄下來,希望能夠給后來者一些幫助乏矾。
架構(gòu) \ 包
本爬蟲主要是用Scarpy架構(gòu)
lxml(使用XPath需要的包)
數(shù)據(jù)存儲
主要采用MongoDB數(shù)據(jù)庫孟抗,MongoDB數(shù)據(jù)庫在處理這種快速讀寫的數(shù)據(jù)有著非常高的效率,該部分主要通過修改pipelines
來實(shí)現(xiàn):
import pymongo
class MongoPipeline(object):
def __init__(self,mongo_uri,mongo_db):
self.mongo_uri = mongo_uri
self.mongo_db = mongo_db
@classmethod
def from_crawler(cls,crawler):
return cls(
mongo_db=crawler.settings.get('MONGO_DB'),
mongo_uri=crawler.settings.get('MONGO_URI')
)
def open_spider(self,spider):
self.client = pymongo.MongoClient(self.mongo_uri)
self.db = self.client[self.mongo_db]
def process_item(self,item,spider):
self.db[item.collection].insert(dict(item))
return item
def close_spider(self,spider):
self.client.close()
起點(diǎn)網(wǎng)的反爬機(jī)制
無論怎么看钻心,起點(diǎn)網(wǎng)的反爬機(jī)制都是很業(yè)余的凄硼,我本來以為需要構(gòu)建一個(gè)IP池,或者說限制住訪問速度才能爬的下來捷沸;但這些都沒有用到摊沉,基本上只要寫完基本的Scrapy我們就能得到所需要的數(shù)據(jù)。
但在爬取網(wǎng)站數(shù)據(jù)的時(shí)候痒给,我遇到了相當(dāng)多次訪問連接失效的問題说墨,不管是使用爬蟲訪問還是直接用瀏覽器訪問骏全,這種情況都不少,對此我認(rèn)為起點(diǎn)網(wǎng)的后臺代碼也許寫的真的很爛尼斧,但就是這么爛的代碼支撐起了中國網(wǎng)絡(luò)文學(xué)界的半壁江山姜贡。果然在業(yè)務(wù)確定、行業(yè)壁壘已經(jīng)建立的情況下棺棵,只要代碼能跑得動(dòng)楼咳,就不需要大修。
那么起點(diǎn)網(wǎng)主要使用了哪些反爬機(jī)制呢烛恤?
1. 數(shù)字亂碼
我們可以通過F12鍵打開控制臺母怜,在Elements中查看到這一反爬機(jī)制;此處我已經(jīng)截圖缚柏,我們可以看到:
原本在瀏覽器中清晰可見的數(shù)字糙申,在element(HTML)代碼中變成了亂碼,這是起點(diǎn)給予的第一個(gè)反爬機(jī)制船惨。
<center>源碼</center>
原本我以為是由于編碼的問題導(dǎo)致的控制臺查看出現(xiàn)了亂碼柜裸,在后臺我看到數(shù)字的編碼為𘡁
等,開始我嘗試去搜索這是什么編碼粱锐,走了很多彎路后發(fā)現(xiàn)這其實(shí)是一個(gè)映射疙挺。源碼對應(yīng)的字符部分其實(shí)表示的是某種字體的英文顯示,如在截圖中怜浅,該字體為KbUxwPAi
铐然;了解到這些,我們要做的就是構(gòu)建起這個(gè)映射恶座,然后解碼出對應(yīng)的數(shù)字搀暑,該部分的思路如下:
獲取頁面的字體文件鏈接(通過正則表達(dá)式)
通過fontTool木塊獲取當(dāng)前字體映射關(guān)系
建立起英文映射
通過pyquery進(jìn)行數(shù)據(jù)提取
該部分代碼如下(spider中的qidian.py)
def get_font(self,Pageurl):
response = requests.get(Pageurl).text
doc = pq(response)
# 獲取當(dāng)前字體文件名稱
fonturl = doc('p.update > span > style').text()
url = re.search('woff.*?url.*?\'(.+?)\'.*?truetype',fonturl).group(1)
response = requests.get(url)
font = TTFont(BytesIO(response.content))
cmap = font.getBestCmap()
font.close()
return cmap
def get_encode(self,cmap,values):
WORD_MAP = {'zero': '0', 'one': '1',
'two': '2', 'three': '3',
'four': '4', 'five': '5',
'six': '6','seven': '7',
'eight': '8', 'nine': '9',
'period': '.'}
word_count = ''
for value in values.split(';'):
value = value[2:]
key = cmap[int(value)]
word_count += WORD_MAP[key]
return word_count
2. 鏈接轉(zhuǎn)換
因?yàn)槠瘘c(diǎn)小說網(wǎng)全部分類下面雖然有顯示五萬多頁的作品,但一般爬蟲抓到5000多頁就會發(fā)現(xiàn)沒有辦法獲取到有效數(shù)據(jù)了跨琳,也就是說全部分類下的5000頁之后的數(shù)據(jù)基本都是擺設(shè)自点,對此我換了個(gè)思路想先抓取到一二級分類的所有內(nèi)容之后在通過更換url中的page=?
來更多的抓取數(shù)據(jù)。
但這也就碰到了第二條反爬機(jī)制
我們直接單擊玄幻
分類可以跳轉(zhuǎn)到玄幻分類的第一頁脉让,此時(shí)鏈接是這個(gè)樣子的:
https://www.qidian.com/all?page=1&style=1&pageSize=20&siteid=1&pubflag=0&hiddenField=0
而如果我們打開第二頁會發(fā)現(xiàn)鏈接變成如下所示
https://www.qidian.com/all?chanId=21&orderId=&style=1&pageSize=20&siteid=1&pubflag=0&hiddenField=0&page=2
對比可以發(fā)現(xiàn)桂敛,page更換了位置,其實(shí)只要細(xì)心點(diǎn)就能繞過這個(gè)坑溅潜;解決這個(gè)小問題之后术唬,之后的爬取就暢通無阻了。
但因?yàn)榫W(wǎng)絡(luò)鏈接的不穩(wěn)定和代碼不夠健壯的問題滚澜,其實(shí)離爬取起點(diǎn)的全網(wǎng)數(shù)據(jù)還是有一定距離的粗仓,起點(diǎn)全網(wǎng)有112萬本小說,我們最終爬取的小說數(shù)目為19萬左右,除去其有一些小說一直沒有展示出來借浊,代碼還是不夠健壯眶掌。
其他
需要著重強(qiáng)調(diào)的部分我已經(jīng)說的差不多了,剩下的諸如定義Items等都是Scrapy的基礎(chǔ)巴碗,在這里就不過多的贅述朴爬,在寫完代碼之后我們可以直接運(yùn)行
scrapy crawl qidian
來抓取運(yùn)行爬蟲,之后你會看到數(shù)據(jù)如激流一般在屏幕上流淌
以上橡淆。
github源碼地址:起點(diǎn)小說網(wǎng)全站爬蟲