Python爬蟲:爬取JS加載數(shù)據(jù)的網(wǎng)頁(yè)

比如簡(jiǎn)書:

Paste_Image.png

我們來(lái)寫個(gè)程序祭阀,爬取簡(jiǎn)書網(wǎng)站隨便一個(gè)作者的所有文章盟榴,再對(duì)其所有文章進(jìn)行分詞統(tǒng)計(jì)
程序運(yùn)行統(tǒng)計(jì)的結(jié)果見(jiàn)文章:
我統(tǒng)計(jì)了彭小六簡(jiǎn)書360篇文章中使用的詞語(yǔ)

需要的Python包

包名 作用
selenium 用于和phantomjs合作模擬瀏覽器訪問(wèn)網(wǎng)頁(yè)
lxml 用于對(duì)html頁(yè)面的解析倔监,提取數(shù)據(jù)
jieba 用于對(duì)文章正文分詞
tld 解析url, 比如提取domain

還需要下載 phantomjs义矛,selenium配合phantomjs的使用代碼中有體現(xiàn)
下載地址: http://phantomjs.org/

下面代碼中派诬,由于使用文件保存數(shù)據(jù),而沒(méi)有使用數(shù)據(jù)庫(kù)保存數(shù)據(jù)圈纺,所以代碼量比較多秦忿,其中主要代碼并不多

直接上代碼####

# -*-coding:utf-8-*-
import json
import os, sys
from random import randint
from collections import Counter
import jieba
from lxml import etree
from selenium import webdriver
import time
from tld import get_tld
path = os.path.abspath(os.path.dirname(__file__))

class Spider():
    '''
    獲取簡(jiǎn)書作者的全部文章頁(yè)面,并解析
    '''
    def __init__(self, start_url):
        '''
        我這里使用文件保存數(shù)據(jù)蛾娶,沒(méi)有使用數(shù)據(jù)庫(kù)保存數(shù)據(jù)
        所有需要初始化文件保存路徑
        使用本程序的你可以把文件保存改成數(shù)據(jù)庫(kù)保存,建議使用nosql方便保存
        start_url:作者文章列表頁(yè)面灯谣,比如http://www.reibang.com/u/65fd4e5d930d
        :return:
        '''
        self.start_url = start_url
        res = get_tld(self.start_url, as_object=True, fix_protocol=True)
        self.domain = "{}.{}".format(res.subdomain, res.tld)

        self.user_id = self.start_url.split("/")[-1]
       
        # 保存作者文章列表html頁(yè)面
        post_list_dir = '{}/post-list'.format(path)
        self.post_lists_html = '{}/post_list_{}.html'.format(post_list_dir, self.user_id)
        # 保存作者所有文章的url
        self.post_lists_urls = '{}/urls_{}.dat'.format(post_list_dir, self.user_id)
        # 保存文章原始網(wǎng)頁(yè):
        self.posts_html_dir = '{}/post-html/{}'.format(path, self.user_id)
        # 保存文章解析后的內(nèi)容:
        self.posts_data_dir = '{}/post-data/{}'.format(path,self.user_id)
        # 保存文章統(tǒng)計(jì)后的結(jié)果:
        self.result_dir = '{}/result'.format(path)

        self.executable_path='{}/phantomjs-2.1.1-linux-x86_64/bin/phantomjs'.format(path)
        # mkdir
        if not os.path.exists(self.posts_html_dir):
            os.makedirs(self.posts_html_dir)
        if not os.path.exists(self.posts_data_dir):
            os.makedirs(self.posts_data_dir)
        if not os.path.exists(post_list_dir):
            os.makedirs(post_list_dir)
        if not os.path.exists(self.result_dir):
            os.makedirs(self.result_dir)
        # 網(wǎng)上隨筆找的免費(fèi)代理ip
        self.ips = ['61.167.222.17:808','58.212.121.72:8998', '111.1.3.36:8000', '125.117.133.74:9000']

    def post_list_page(self):
        '''
        獲取文章列表頁(yè)面,以及文章鏈接
        :return:
        '''
        obj = webdriver.PhantomJS(executable_path=self.executable_path)
        obj.set_page_load_timeout(30)
        obj.maximize_window()
        # 隨機(jī)一個(gè)代理ip
        ip_num = len(self.ips)
        ip = self.ips[randint(0,ip_num-1)]
        obj.http_proxy = ip
    
        obj.get(self.start_url)

        # 文章總數(shù)量
        sel = etree.HTML(obj.page_source)
        r = sel.xpath("http://div[@class='main-top']//div[@class='info']//li[3]//p//text()")
        if r:
            crawl_post_n = int(r[0])
        else:
            print("[Error] 提取文章總書的xpath不正確")
            sys.exit()
        n = crawl_post_n/9
        i = 1
        while n:
            t = randint(2,5)
            time.sleep(t)
            js = "var q=document.body.scrollTop=100000"
            # 頁(yè)面一直下滾
            obj.execute_script(js)
            n -= 1
            i += 1
        # 然后把作者文章列表頁(yè)面的html(保存到數(shù)據(jù)庫(kù)蛔琅,或文本保存)
        of = open(self.post_lists_html, "w")
        of.write(obj.page_source)
        of.close()

        # 我們也順便把作者所有的文章鏈接提取出來(lái)(保存到數(shù)據(jù)庫(kù)胎许,或文本保存)
        of = open(self.post_lists_urls, "w")
        sel = etree.HTML(obj.page_source)
        results = sel.xpath("http://div[@id='list-container']//li//a[@class='title']/@href")
        for result in results:
            of.write("http://{}{}".format(self.domain, result.strip()))
            of.write("\n")
        of.close()

    def posts_html(self):
        '''
        獲取文章頁(yè)面html
        :return:
        '''
        of = open(self.post_lists_urls)
        urls = of.readlines()
        
        ip_num = len(self.ips)
        obj = webdriver.PhantomJS(executable_path=self.executable_path)
        obj.set_page_load_timeout(10)
        obj.maximize_window()
        for url in urls:
            # 隨機(jī)一個(gè)代理ip
            ip = self.ips[randint(0,ip_num-1)]
            obj.http_proxy = ip
            url = url.strip()
            print("代理ip:{}".format(ip))
            print("網(wǎng)頁(yè):{}".format(url))

            try:
                obj.get(url)
            except:
                print("Error:{}".format(url))

            post_id = url.split("/")[-1]
            of = open("{}/{}_{}.html".format(self.posts_html_dir, obj.title, post_id), "w")
            of.write(obj.page_source)
            of.close()
            t = randint(1,5)
            time.sleep(t)

    def page_parsing(self):
        '''
        html解析
        :return:
        '''
        # 只獲取匹配的第一個(gè)
        xpath_rule_0 ={
            "author":"http://div[@class='author']//span[@class='name']//text()", # 作者名字
            "author_tag":"http://div[@class='author']//span[@class='tag']//text()",# 作者標(biāo)簽
            "postdate":"http://div[@class='author']//span[@class='publish-time']//text()", # 發(fā)布時(shí)間
            "word_num":"http://div[@class='author']//span[@class='wordage']//text()",#字?jǐn)?shù)
            "notebook":"http://div[@class='show-foot']//a[@class='notebook']/span/text()",#文章屬于的目錄
            "title":"http://div[@class='article']/h1[@class='title']//text()",#文章標(biāo)題
        }
        # 獲取匹配的所有,并拼接成一個(gè)字符串的
        xpath_rule_all_tostr ={
            "content":"http://div[@class='show-content']//text()",#正文
        }
        # 獲取匹配的所有,保存數(shù)組形式
        xpath_rule_all ={
            "collection":"http://div[@class='include-collection']//a[@class='item']//text()",#收入文章的專題
        }
        # 遍歷所有文章的html文件,如果保存在數(shù)據(jù)庫(kù)的則直接查詢出來(lái)
        list_dir =  os.listdir(self.posts_html_dir)
        for file in list_dir:
            file = "{}/{}".format(self.posts_html_dir, file)
            if os.path.isfile(file):
                of = open(file)
                html = of.read()
                sel = etree.HTML(html)
                of.close()

                # 解析
                post_id = file.split("_")[-1].strip(".html")
                doc = {'url':'http://{}/p/{}'.format(self.domain,post_id)}
                for k,rule in xpath_rule_0.items():
                    results = sel.xpath(rule)
                    if results:
                        doc[k] = results[0]
                    else:
                        doc[k] = None

                for k,rule in xpath_rule_all_tostr.items():
                    results = sel.xpath(rule)
                    if results:
                        doc[k] = ""
                        for result in results:
                            if result.strip():
                                doc[k] = "{}{}".format(doc[k], result)
                    else:
                        doc[k] = None

                for k,rule in xpath_rule_all.items():
                    results = sel.xpath(rule)
                    if results:
                        doc[k] = results
                    else:
                        doc[k] = None
                if doc["word_num"]:
                    doc["word_num"] = int(doc["word_num"].strip('字?jǐn)?shù)').strip())
                else:
                    doc["word_num"] = 0

                # 保存到數(shù)據(jù)庫(kù)或者文件中

                of = open("{}/{}.json".format(self.posts_data_dir, post_id), "w")
                of.write(json.dumps(doc))
                of.close()

    def statistics(self):
        '''
        分開對(duì)每篇文章的進(jìn)行分詞統(tǒng)計(jì)罗售,也統(tǒng)計(jì)全部文章分詞
        :return: 
        '''
        # 遍歷所有文章的html文件辜窑,如果保存在數(shù)據(jù)庫(kù)的則直接查詢出來(lái)
        word_sum = {} #正文全部詞語(yǔ)統(tǒng)計(jì)
        title_word_sum = {} #標(biāo)題全部詞語(yǔ)統(tǒng)計(jì)
        post_word_cnt_list = [] #每篇文章使用的詞匯數(shù)量

        # 正文統(tǒng)計(jì)數(shù)據(jù)保存
        list_dir = os.listdir(self.posts_data_dir)
        for file in list_dir:
            file = "{}/{}".format(self.posts_data_dir, file)
            if os.path.isfile(file):

                of = open(file)
                str = of.read()
                doc = json.loads(str)
                # 正文統(tǒng)計(jì):精確模式,默認(rèn)hi精確模式,所以可以不指定cut_all=False
                words = jieba.cut(doc["content"], cut_all=False)
                data = dict(Counter(words))
                data = sorted(data.iteritems(), key=lambda d: d[1], reverse=True)
                word_cnt = 0
                for w in data:
                    # 只統(tǒng)計(jì)超過(guò)1個(gè)字的詞語(yǔ)
                    if len(w[0]) < 2:
                        continue
                    # 統(tǒng)計(jì)到全部文章詞語(yǔ)中
                    if w[0] in word_sum:
                        word_sum[w[0]]["cnt"] += w[1]
                        word_sum[w[0]]["post_cnt"] += 1
                    else:
                        word_sum[w[0]] = {}
                        word_sum[w[0]]["cnt"] = w[1]
                        word_sum[w[0]]["post_cnt"] = 1

                    word_cnt += 1

                post_word_cnt_list.append((word_cnt,
                                           doc["postdate"],
                                           doc["title"],
                                           doc["url"]))

                # 標(biāo)題統(tǒng)計(jì):精確模式,默認(rèn)hi精確模式寨躁,所以可以不指定cut_all=False
                words = jieba.cut(doc["title"], cut_all=False)
                data = dict(Counter(words))
                data = sorted(data.iteritems(), key=lambda d: d[1], reverse=True)
                for w in data:
                    # 只統(tǒng)計(jì)超過(guò)1個(gè)字的詞語(yǔ)
                    if len(w[0]) < 2:
                        continue
                    # 統(tǒng)計(jì)到全部文章詞語(yǔ)中
                    if w[0] in title_word_sum:
                        title_word_sum[w[0]]["cnt"] += w[1]
                        title_word_sum[w[0]]["post_cnt"] += 1
                    else:
                        title_word_sum[w[0]] = {}
                        title_word_sum[w[0]]["cnt"] = w[1]
                        title_word_sum[w[0]]["post_cnt"] = 1

                post_word_cnt_list = sorted(post_word_cnt_list, key=lambda d: d[0], reverse=True)
        wf = open("{}/content_statis_{}.dat".format(self.result_dir, self.user_id), "w")
        wf.write("| 詞語(yǔ) | 發(fā)布日期 | 標(biāo)題 | 鏈接 |\n")
        for pw in post_word_cnt_list:
            wf.write("| {} | {} | {}| {}|\n".format(pw[0],pw[1],pw[2],pw[3]))
        wf.close()

        # 全部文章正文各詞語(yǔ) 按使用次數(shù) 統(tǒng)計(jì)結(jié)果
        wf = open("{}/content_statis_sum_use-num_{}.dat".format(self.result_dir, self.user_id), "w")
        word_sum_t = sorted(word_sum.iteritems(), key=lambda d: d[1]['cnt'], reverse=True)
        wf.write("| 分詞 | 使用次數(shù) | 使用的文章數(shù)量|\n")
        for w in word_sum_t:
            wf.write("| {} | {} | {}|\n".format(w[0], w[1]["cnt"], w[1]["post_cnt"]))
        wf.close()

        # 全部文章正文各詞語(yǔ) 按使用文章篇數(shù) 統(tǒng)計(jì)結(jié)果
        wf = open("{}/content_statis_sum_post-num_{}.dat".format(self.result_dir, self.user_id), "w")
        word_sum_t = sorted(word_sum.iteritems(), key=lambda d: d[1]['post_cnt'], reverse=True)
        wf.write("| 分詞 | 使用的文章數(shù)量 | 使用次數(shù) |\n")
        for w in word_sum_t:
            wf.write("| {} | {} | {}|\n".format(w[0], w[1]["post_cnt"], w[1]["cnt"]))
        wf.close()


        # 全部文章title各詞語(yǔ) 按使用次數(shù) 統(tǒng)計(jì)結(jié)果
        wf = open("{}/title_statis_sum_use-num_{}.dat".format(self.result_dir,self.user_id), "w")
        title_word_sum_t = sorted(title_word_sum.iteritems(), key=lambda d: d[1]['cnt'], reverse=True)
        wf.write("| 分詞 | 使用次數(shù) | 使用的文章數(shù)量|\n")
        for w in title_word_sum_t:
            wf.write("| {} | {} | {}|\n".format(w[0], w[1]["cnt"], w[1]["post_cnt"]))
        wf.close()

        # 全部文章title各詞語(yǔ) 按使用次數(shù) 統(tǒng)計(jì)結(jié)果
        wf = open("{}/title_statis_sum_post-num_{}.dat".format(self.result_dir, self.user_id), "w")
        title_word_sum_t = sorted(title_word_sum.iteritems(), key=lambda d: d[1]['post_cnt'], reverse=True)
        wf.write("| 分詞 | 使用的文章數(shù)量 | 使用次數(shù) |\n")
        for w in title_word_sum_t:
            wf.write("| {} | {} | {}|\n".format(w[0], w[1]["post_cnt"], w[1]["cnt"]))
        wf.close()
        print("一共統(tǒng)計(jì)文章:{} 篇".format(len(list_dir)))
        print("所有正文-使用了2字及以上詞語(yǔ):{} 個(gè)".format(len(word_sum_t)))
        print("所有標(biāo)題-使用了2字及以上詞語(yǔ):{} 個(gè)".format(len(title_word_sum_t)))

if __name__ == '__main__':
    sp = Spider(start_url="http://www.reibang.com/u/65fd4e5d930d")
    print("獲取作者文章列表頁(yè)面...")
    sp.post_list_page()
    print("獲取作者所有文章頁(yè)面...")
    #sp.posts_html()
    print("解析作者所有文章頁(yè)面...")
    #sp.page_parsing()
    print("簡(jiǎn)單統(tǒng)計(jì)分析文章詞匯...")
    #sp.statistics()

程序運(yùn)行統(tǒng)計(jì)的結(jié)果見(jiàn)文章: 我統(tǒng)計(jì)了彭小六簡(jiǎn)書360篇文章中使用的詞語(yǔ)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末穆碎,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子职恳,更是在濱河造成了極大的恐慌所禀,老刑警劉巖,帶你破解...
    沈念sama閱讀 219,039評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件放钦,死亡現(xiàn)場(chǎng)離奇詭異色徘,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)最筒,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,426評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)蔚叨,“玉大人床蜘,你說(shuō)我怎么就攤上這事∶锼” “怎么了邢锯?”我有些...
    開封第一講書人閱讀 165,417評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)搀别。 經(jīng)常有香客問(wèn)我丹擎,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,868評(píng)論 1 295
  • 正文 為了忘掉前任蒂培,我火速辦了婚禮再愈,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘护戳。我一直安慰自己翎冲,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,892評(píng)論 6 392
  • 文/花漫 我一把揭開白布媳荒。 她就那樣靜靜地躺著抗悍,像睡著了一般。 火紅的嫁衣襯著肌膚如雪钳枕。 梳的紋絲不亂的頭發(fā)上缴渊,一...
    開封第一講書人閱讀 51,692評(píng)論 1 305
  • 那天,我揣著相機(jī)與錄音鱼炒,去河邊找鬼衔沼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛田柔,可吹牛的內(nèi)容都是我干的俐巴。 我是一名探鬼主播,決...
    沈念sama閱讀 40,416評(píng)論 3 419
  • 文/蒼蘭香墨 我猛地睜開眼硬爆,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼欣舵!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起缀磕,我...
    開封第一講書人閱讀 39,326評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤缘圈,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后袜蚕,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體糟把,經(jīng)...
    沈念sama閱讀 45,782評(píng)論 1 316
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,957評(píng)論 3 337
  • 正文 我和宋清朗相戀三年牲剃,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了遣疯。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,102評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡凿傅,死狀恐怖缠犀,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情聪舒,我是刑警寧澤辨液,帶...
    沈念sama閱讀 35,790評(píng)論 5 346
  • 正文 年R本政府宣布,位于F島的核電站箱残,受9級(jí)特大地震影響滔迈,放射性物質(zhì)發(fā)生泄漏止吁。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,442評(píng)論 3 331
  • 文/蒙蒙 一燎悍、第九天 我趴在偏房一處隱蔽的房頂上張望敬惦。 院中可真熱鬧,春花似錦间涵、人聲如沸仁热。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,996評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)抗蠢。三九已至,卻和暖如春思劳,著一層夾襖步出監(jiān)牢的瞬間迅矛,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,113評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工潜叛, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留秽褒,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,332評(píng)論 3 373
  • 正文 我出身青樓威兜,卻偏偏與公主長(zhǎng)得像销斟,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子椒舵,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,044評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容

  • Android 自定義View的各種姿勢(shì)1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,167評(píng)論 25 707
  • 在本文中我會(huì)嘗試如何從0數(shù)據(jù)開始到獲取百萬(wàn)頁(yè)面蚂踊,進(jìn)行用戶數(shù)據(jù)分析、建模笔宿,再數(shù)據(jù)信息化犁钟、可視化,生成用戶畫像分析用戶...
    hirainchen閱讀 25,306評(píng)論 121 690
  • 1. 前言 這篇文章藏在心中已經(jīng)好一段時(shí)日了泼橘,遲遲不敢動(dòng)筆涝动,主要是擔(dān)心不知道該如何去組織這樣一篇技術(shù)文章。 其實(shí)個(gè)...
    lanzhiheng閱讀 5,358評(píng)論 19 48
  • 一直以來(lái)我都覺(jué)得我像一只狐貍炬灭。高傲且孤獨(dú)醋粟。別人看到的只是我美麗妖艷的外表。沒(méi)錯(cuò)重归。我熱衷于迷惑人米愿。讓他們神魂...
    古月女喬閱讀 321評(píng)論 0 0
  • 2017年12月28日 星期四 天氣:多云 潘紫涵媽媽親子日記 今天班級(jí)群里通知,明天學(xué)校統(tǒng)一慶祝元旦...
    潘紫涵媽媽閱讀 156評(píng)論 0 2