美文网首页人人可以学Python@IT·互联网程序员
Python爬虫:爬取JS加载数据的网页

Python爬虫:爬取JS加载数据的网页

作者: HiWoo | 来源:发表于2017-05-09 12:46 被阅读554次

    比如简书:

    Paste_Image.png

    我们来写个程序,爬取简书网站随便一个作者的所有文章,再对其所有文章进行分词统计
    程序运行统计的结果见文章:
    我统计了彭小六简书360篇文章中使用的词语

    需要的Python包

    包名 作用
    selenium 用于和phantomjs合作模拟浏览器访问网页
    lxml 用于对html页面的解析,提取数据
    jieba 用于对文章正文分词
    tld 解析url, 比如提取domain

    还需要下载 phantomjs,selenium配合phantomjs的使用代码中有体现
    下载地址: http://phantomjs.org/

    下面代码中,由于使用文件保存数据,而没有使用数据库保存数据,所以代码量比较多,其中主要代码并不多

    直接上代码####

    # -*-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():
        '''
        获取简书作者的全部文章页面,并解析
        '''
        def __init__(self, start_url):
            '''
            我这里使用文件保存数据,没有使用数据库保存数据
            所有需要初始化文件保存路径
            使用本程序的你可以把文件保存改成数据库保存,建议使用nosql方便保存
            start_url:作者文章列表页面,比如http://www.jianshu.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页面
            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)
            # 保存文章原始网页:
            self.posts_html_dir = '{}/post-html/{}'.format(path, self.user_id)
            # 保存文章解析后的内容:
            self.posts_data_dir = '{}/post-data/{}'.format(path,self.user_id)
            # 保存文章统计后的结果:
            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)
            # 网上随笔找的免费代理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):
            '''
            获取文章列表页面,以及文章链接
            :return:
            '''
            obj = webdriver.PhantomJS(executable_path=self.executable_path)
            obj.set_page_load_timeout(30)
            obj.maximize_window()
            # 随机一个代理ip
            ip_num = len(self.ips)
            ip = self.ips[randint(0,ip_num-1)]
            obj.http_proxy = ip
        
            obj.get(self.start_url)
    
            # 文章总数量
            sel = etree.HTML(obj.page_source)
            r = sel.xpath("//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"
                # 页面一直下滚
                obj.execute_script(js)
                n -= 1
                i += 1
            # 然后把作者文章列表页面的html(保存到数据库,或文本保存)
            of = open(self.post_lists_html, "w")
            of.write(obj.page_source)
            of.close()
    
            # 我们也顺便把作者所有的文章链接提取出来(保存到数据库,或文本保存)
            of = open(self.post_lists_urls, "w")
            sel = etree.HTML(obj.page_source)
            results = sel.xpath("//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):
            '''
            获取文章页面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:
                # 随机一个代理ip
                ip = self.ips[randint(0,ip_num-1)]
                obj.http_proxy = ip
                url = url.strip()
                print("代理ip:{}".format(ip))
                print("网页:{}".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:
            '''
            # 只获取匹配的第一个
            xpath_rule_0 ={
                "author":"//div[@class='author']//span[@class='name']//text()", # 作者名字
                "author_tag":"//div[@class='author']//span[@class='tag']//text()",# 作者标签
                "postdate":"//div[@class='author']//span[@class='publish-time']//text()", # 发布时间
                "word_num":"//div[@class='author']//span[@class='wordage']//text()",#字数
                "notebook":"//div[@class='show-foot']//a[@class='notebook']/span/text()",#文章属于的目录
                "title":"//div[@class='article']/h1[@class='title']//text()",#文章标题
            }
            # 获取匹配的所有,并拼接成一个字符串的
            xpath_rule_all_tostr ={
                "content":"//div[@class='show-content']//text()",#正文
            }
            # 获取匹配的所有,保存数组形式
            xpath_rule_all ={
                "collection":"//div[@class='include-collection']//a[@class='item']//text()",#收入文章的专题
            }
            # 遍历所有文章的html文件,如果保存在数据库的则直接查询出来
            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('字数').strip())
                    else:
                        doc["word_num"] = 0
    
                    # 保存到数据库或者文件中
    
                    of = open("{}/{}.json".format(self.posts_data_dir, post_id), "w")
                    of.write(json.dumps(doc))
                    of.close()
    
        def statistics(self):
            '''
            分开对每篇文章的进行分词统计,也统计全部文章分词
            :return: 
            '''
            # 遍历所有文章的html文件,如果保存在数据库的则直接查询出来
            word_sum = {} #正文全部词语统计
            title_word_sum = {} #标题全部词语统计
            post_word_cnt_list = [] #每篇文章使用的词汇数量
    
            # 正文统计数据保存
            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)
                    # 正文统计:精确模式,默认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:
                        # 只统计超过1个字的词语
                        if len(w[0]) < 2:
                            continue
                        # 统计到全部文章词语中
                        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"]))
    
                    # 标题统计:精确模式,默认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:
                        # 只统计超过1个字的词语
                        if len(w[0]) < 2:
                            continue
                        # 统计到全部文章词语中
                        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("| 词语 | 发布日期 | 标题 | 链接 |\n")
            for pw in post_word_cnt_list:
                wf.write("| {} | {} | {}| {}|\n".format(pw[0],pw[1],pw[2],pw[3]))
            wf.close()
    
            # 全部文章正文各词语 按使用次数 统计结果
            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("| 分词 | 使用次数 | 使用的文章数量|\n")
            for w in word_sum_t:
                wf.write("| {} | {} | {}|\n".format(w[0], w[1]["cnt"], w[1]["post_cnt"]))
            wf.close()
    
            # 全部文章正文各词语 按使用文章篇数 统计结果
            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("| 分词 | 使用的文章数量 | 使用次数 |\n")
            for w in word_sum_t:
                wf.write("| {} | {} | {}|\n".format(w[0], w[1]["post_cnt"], w[1]["cnt"]))
            wf.close()
    
    
            # 全部文章title各词语 按使用次数 统计结果
            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("| 分词 | 使用次数 | 使用的文章数量|\n")
            for w in title_word_sum_t:
                wf.write("| {} | {} | {}|\n".format(w[0], w[1]["cnt"], w[1]["post_cnt"]))
            wf.close()
    
            # 全部文章title各词语 按使用次数 统计结果
            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("| 分词 | 使用的文章数量 | 使用次数 |\n")
            for w in title_word_sum_t:
                wf.write("| {} | {} | {}|\n".format(w[0], w[1]["post_cnt"], w[1]["cnt"]))
            wf.close()
            print("一共统计文章:{} 篇".format(len(list_dir)))
            print("所有正文-使用了2字及以上词语:{} 个".format(len(word_sum_t)))
            print("所有标题-使用了2字及以上词语:{} 个".format(len(title_word_sum_t)))
    
    if __name__ == '__main__':
        sp = Spider(start_url="http://www.jianshu.com/u/65fd4e5d930d")
        print("获取作者文章列表页面...")
        sp.post_list_page()
        print("获取作者所有文章页面...")
        #sp.posts_html()
        print("解析作者所有文章页面...")
        #sp.page_parsing()
        print("简单统计分析文章词汇...")
        #sp.statistics()
    

    程序运行统计的结果见文章: 我统计了彭小六简书360篇文章中使用的词语

    相关文章

      网友评论

      • 13cf2958cecc:网页:http://www.jianshu.com/p/c988fd11ed8a
        E:\Pickup-master\Pickup-master/post-html/65fd4e5d930d/我统计了彭小六简书360篇文章中使用的词语-Top50 - 简书_c988fd11ed8a.html
        Traceback (most recent call last):
        File "E:/Pickup-master/Pickup-master/test.py", line 316, in <module>
        sp.posts_html()
        File "E:/Pickup-master/Pickup-master/test.py", line 132, in posts_html
        of = open("{}/{}_{}.html".format(self.posts_html_dir, obj.title, post_id), "w")
        IOError: [Errno 22] invalid mode ('w') or filename: 'E:\\Pickup-master\\Pickup-master/post-html/65fd4e5d930d/\xe6\x88\x91\xe7\xbb\x9f\xe8\xae\xa1\xe4\xba\x86\xe5\xbd\xad\xe5\xb0\x8f\xe5\x85\xad\xe7\xae\x80\xe4\xb9\xa6360\xe7\xaf\x87\xe6\x96\x87\xe7\xab\xa0\xe4\xb8\xad\xe4\xbd\xbf\xe7\x94\xa8\xe7\x9a\x84\xe8\xaf\x8d\xe8\xaf\xad-Top50 - \xe7\xae\x80\xe4\xb9\xa6_c988fd11ed8a.html'
        HiWoo:@zbdess 我用的是Linux系统运行,你用的是Windows,所以报错,因为Linux和windows不兼容,比如你这里的path
      • 13cf2958cecc:Traceback (most recent call last):
        File "C:/Users/Administrator/AppData/Roaming/Python/Python27/site-packages/selenium/webdriver/phantomjs/webdriver.py", line 20, in <module>
        from .service import Service
        ValueError: Attempted relative import in non-package
      • 13cf2958cecc:你的这个程序是用的python什么版本
        HiWoo:@zbdess 2.7.6

      本文标题:Python爬虫:爬取JS加载数据的网页

      本文链接:https://www.haomeiwen.com/subject/gsmbtxtx.html