美文网首页python爬虫android菜鸟低调修行纪念册测试开发
新手向爬虫(一)利用工具轻松爬取简书并分析

新手向爬虫(一)利用工具轻松爬取简书并分析

作者: treelake | 来源:发表于2016-11-03 22:58 被阅读7054次

    爬取目标

    爬取四块简单的简书网页,并做一定的分析。

    • 第一块是首页热门,网址就是
      http://jianshu.com
    • 第二块是简书推荐,网址形如
      http://www.jianshu.com/recommendations/notes?max_id=1477985000
    • 第三块是热门专题页,网址形如
      http://www.jianshu.com/collections?order_by=score&page=1
    • 第四块就是简书专题文章,网址形如
      http://www.jianshu.com/collections/16/notes?order_by=added_at&page=1

    网络请求和解析使用库

    • 从最简单的开始,使用最好用的requests做网络访问,并且使用操作方便的beautifulsoup来解析html。
    • 没有的话直接pip install requestspip install bs4安装。

    数据存储

    • 直接存储为csv。在Excel文件中打开并分析。
    • 此外尝试使用下tinydb,一个文档型数据库。操作简易。(只在部分内容使用到)

    首先我们做最简单的第四块

    • 从网址http://www.jianshu.com/collections/16/notes?order_by=added_at&page=1的结构,就可以看出我们只需要修改网址末尾的page=x就可以不断地访问新的页面爬取这个专题下的文章了,collections/后面的16代表程序员专题,order_by=added_at代表按收录时间排序。下面就开始爬取吧。
    import requests
    from bs4 import BeautifulSoup
    # 引入库
    base_url = 'http://www.jianshu.com/collections/16/notes?order_by=added_at&page=%d' # page=0 和 page=1的效果相同
    add_url = 1 # page数
    num = 0 # 文章标题计数
    
    while(True):
        try:
            page = requests.request('get', base_url% add_url).content
            # 将基础网址和页数拼接得到实际网址,使用request的get方法获取响应,再用content方法得到内容
            soup = BeautifulSoup(page) 
            # 使用BeautifulSoup分析页面,这里并没有指定解析器,会自动选取可获得的最优解析器
            # BeautifulSoup会报一个warning要你选择解析器,可以忽略
            
            article_list = [i.get_text() for i in soup.select(".title a")]
            # 这里使用了BeautifulSoup的选择器soup.select,它会返回一个被选取的对象列表,我们使用列表推导,对每个对象获取其中的文字再组成列表。
            # 选择器使用见下文
            for i in article_list:
            # 将文章列表打印出来
                num+=1
                print(num, '  ', i)
            add_url += 1
        except Exception as e:
        # 异常分析,打印出异常信息
            print(e)
            break # 这里使用break退出循环,也可注释掉该句,不断循环
    
    • 结果如下:


    • 可以看出我们成功地爬取了内容。其实本次爬取的关键在于soup.select选择器的内容。而选择器内容该如何获取呢,它类同css选择器(规则如下),我们可以分析网页源码,根据规则写出。

    • 但是我们还可以使用更便捷的方法,不需要知道额外的知识就解决,那就是使用SelectorGadget,它的chrome插件,非常好用,安装后点击使用,然后直接在你要爬取的网页上选择你要爬取的内容,这时一系列内容会被标黄,但是可能其中有些你不需要的内容,再点一下它们,它们会被标记为红色,就不会被选择器选中,这时你所需要的选择器内容就在下方显示出来了,如下图,黄色的标题就被选中,最下的蓝色选中文字即为自动生成的选择器内容:

    • 这样,我们成功解决了选择器的问题,获取到了我们需要的内容。

    • 其实还有一个关键点在于这样的网址是如何获取的呢,一般我们访问简书可能并不会遇到这样形式的网址,下文中我们会说明。

    接着让我们来看看首页热门文章

    • 我们先通过上面的方法获取文章标题的选择器,它也是soup.select(".title a")
    • 和上面不同的是,首页的网址只有一个,也没有一个明显的页数递增规律,但是首页下方会有一个更多的按钮。让我们使用F12开发者工具选取审查元素(快捷键Ctrl+Shift+C)选中按钮查看这个按钮的信息。
    • 我们可以看到button标签里有个data-url属性,它是一个相对路径,末尾是page=2,将它加上简书域名,尝试一下,发现就是下一页的信息。于是,我们可以通多获取button标签,读取它的属性来访问以继续爬取。
    • 此外,我们升级一下解析器,让html的解析速度变快些,下载lxml(我下载的是cp35-cp35m-win_amd64.whl版本,安装成功),使用pip install x.whlx为 whl文件名)安装。然后我们可以在Beautifulsoup中指定解析器了。
    • 这次我们将爬取到的首页热门文章标题信息保存为csv文件,以供后续处理。
    import requests
    from bs4 import BeautifulSoup
    import csv # 引入csv模块,读写csv文件
    import datetime # 引入日期时间模块
    
    base_url = 'http://jianshu.com'
    
    def shouYeReMen():
        add_url = '' # 先置为空字符串
        title_list = [] # 文章标题列表
        
        for i in range(15): # 首页热门实际为15页,一开始可以设大些让程序自动退出来看到底有多少页
            try:
                first_page = requests.get(base_url+ add_url).content
                soup = BeautifulSoup(first_page, "lxml")
                title_list += [i.get_text() for i in soup.select(".title a")]
                # 以上和上一个爬虫类同
                try:
                    # print(soup.select(".ladda-button"))
                    add_url = soup.select(".ladda-button")[-1].get("data-url")
                    # 使用get方法获取button的标签属性信息作为额外的url
                    # 这里使用try,在最后一页没有button时会抛出异常
                except:
                    # 在异常处理中通过break退出循环
                    break
            except requests.exceptions.ConnectionError: # 有可能请求过多导致服务器拒绝连接
                status_code = "Connection refused"
                print(status_code)
                time.sleep(60)
                # 我们睡眠一分钟后再爬
        with open(datetime.datetime.now().strftime('%Y_%m_%d-%H_%M_%S')+'.csv', 'w', newline='', encoding='utf-8') as f:
        # 在脚本当前文件目录下新建以当前时间命名的csv文件,使用utf8编码
            writer = csv.writer(f)
            # 传入文件描述符构建csv写入器
            writer.writerow(title_list)
            # 将本次爬取的所有文章列表写在一行中
            
        print("have_done!")
        
    import time
    # 最后,我们循环爬取首页信息,为了减轻服务器负担,也避免爬虫请求被拒绝,我们每隔30秒爬取一次
    while True:
        shouYeReMen()
        time.sleep(30)
    
    • 爬取结果如下


    • 现在我们来处理下这些数据,使用分词并保存热点词汇,在excel中查看热点词随时间变化情况。
    • 我们使用友好的中文分词库结巴分词。使用pip install jieba安装。
    import jieba
    from collections import Counter
    # 引入便捷的Counter函数,可以看作一个计数器,计算元素在列表中的出现次数
    # Counter([1,2,3,1])的输出为Counter({1: 2, 2: 1, 3: 1})
    import csv
    
    def word_frequency(text):
    # 定义词频统计函数
        words = [word for word in jieba.cut(text, cut_all=False) if len(word) >= 2]
        # 这里我们直接使用jieba.cut函数来分析文本的词汇,为了方便,我们先选取2个字以上的词统计
        # 进一步可以自定义词库,去掉与增加某些词
        c = Counter(words)
        # 统计词频,并利用Counter的方法返回频率最高的18个(词汇,频率)的元组列表
        return c.most_common(18)
            
            
    import os; os.chdir(r'E:\python\shouye')
    # 这里我使用了系统库,改变当前所处路径,也就是改到下面读取和保存文件的路径
    import glob
    
    result = open('result.csv', 'a', newline='', encoding='gb2312')
    # 这里编码为gb2312,以便直接在excel中打开查看
    writer = csv.writer(result)
    
    filelist = glob.glob('2016_*.csv')
    # 使用glob获取当前路径下所有以2016_开头的csv文件名的列表,顺序为文件名顺序
    num=1 # 打印输出计数
    for file in filelist:
        with open(file, 'r', encoding='utf-8') as f:
            line = f.readline()
            # 读取存有信息的一行
            row = [(word+'-'+str(freq)) for word, freq in word_frequency(line)]
            writer.writerow(row)
            # 以 word, freq = (w, f) 解包返回的词频数据,并组合word和freq(使用str()将freq由数字转化为字符串类型),写入文件
        print("haha",num)
        num+=1
            
    result.close()
    # 记得关闭文件
    
    • 最后我们打开EXCEL查看result.csv文件,得到如下结果。每条记录的间隔是半分多钟,可以看到简书的出现频率还是挺高的(在我爬的600条数据中,'简书'几乎一直是热词),估计是目前的简书程序员专题活动的影响(它们的文章标题都包含'简书'),同时看出'人生''如何'之类的词出现频率也不低。
      间隔约半分钟的简书热词变化

    再进一步,爬取第三块-热门专题页

    • 我们在浏览器中来到简书的专题广场页面,再次打开发者工具,点击红框切换到Network分析界面。


    • 然后向下滚动简书页面,一小段之后,我们发现网络请求栏有新的条目出现,而变化的第一个的名称就是我们需要的请求地址了。


    • 我们直接copy链接,获得
      http://www.jianshu.com/collections?page=2&_=1478182353282
      Paste_Image.png
    • 我们只取&前面部分就可以访问了,在浏览器里尝试下,ok。这就是获取网络请求地址的最常用方法了。
    • 类似的,在收集到请求信息后我们尝试获取所有简书热门专题信息:
    from tinydb import TinyDB
    db = TinyDB('collection_db.json')
    # 这里我们尝试下tinydb,一句话就获得了新建的数据库对象
    import csv
    csvfile = open("1.csv", "w", encoding='utf-8') # 先保存为utf8编码,以免出现gbk编码错误
    fieldnames = ["cname", "href", "focus_number", "essays", "hot_url", "new_url"] # 设定Excel文件的列表头
    writer = csv.DictWriter(csvfile, fieldnames=fieldnames)
    # 这里我们获取csv的字典插入器,可以直接插入字典,键为列表头中的内容
    writer.writeheader()
    # 写入列表头
    
    import requests
    from bs4 import BeautifulSoup
    import re
    reg = re.compile('(\d+)篇')
    # 使用正则匹配来获取篇数,(\d+)表示多个数字
    
    base_url = "http://jianshu.com"
    collectionlist_url = "http://www.jianshu.com/collections?order_by=score&page=%d"
    # collection_url = 'http://www.jianshu.com/collections/%d/notes?order_by=added_at&page=1'
    add_url = 1
    # collection_num = 1
    
    collection_selector = "h5 a" # 专题名
    focus_on_selector = ".follow > span" # 关注人数
    total_essay_selector = ".blue-link" # 总篇数
    hot_selector = ".top-articles a" # 热门排序
    new_selector = ".latest-articles a" # 最新收入
    
    total_collection_list = []
    
    
    while(add_url < 300):
        try: # 请求并获取各种数据
            first_page = requests.request('get', collectionlist_url% add_url).content
            soup = BeautifulSoup(first_page, "lxml")
            
            collection_list = [[i.get_text(),i.get('href')] for i in soup.select(collection_selector)] # 一组24个
            if not collection_list : break
            
            focus_on_list = [i.get_text() for i in soup.select(focus_on_selector)] 
            focus_on_list = [int(float(i.replace('K', ''))*1000) if ('K' in i) else int(i) for i in focus_on_list]
            # 对尾缀K处理,将K去掉,将数字乘以1000
            
            total_essay_list =[ int( reg.findall(i.get_text())[0] ) for i in soup.select(total_essay_selector)]
            # 使用zip将多个可迭代对象合并在一起使用
            for c,f,e in zip(collection_list, focus_on_list, total_essay_list):
                c.append(f);c.append(e)
                second_page = requests.request('get', base_url+c[1]).content
                # 访问专题页面,获取该专题的热门链接和最新加入文章页的链接。
                soup = BeautifulSoup(second_page, "lxml")
                hot_url = soup.select(hot_selector)[0].get('href')
                new_url = soup.select(new_selector)[0].get('href')
                c += [hot_url, new_url]
                
            for i in collection_list:
                #print(add_url, '  ', i)
                collection_dict = {"cname": i[0], "href":i[1], "focus_number":i[2], "essays":i[3], "hot_url":i[4], "new_url":i[5]}
                db.insert(collection_dict)
                # 一句话插入数据库
                writer.writerow(collection_dict)
                # 写入csv文件
            
            print(add_url)    
            add_url += 1
            total_collection_list += collection_list
            
        except Exception as e:
            print(e)
            break
    
    csvfile.close()
    # 关闭文件
    
    • 这里由于编码问题,直接使用excel打开csv文件会出现乱码。我们采取另外一个简单方式,直接使用该网站

      将之前存入的数据库文件collection_db.json拖入并下载得到我们需要的良好编码的csv文件。打开结果如下:
    • 专题关注人数排序:
    • 以关注人数/文章数比率排序:


    • 结果还是挺有趣的。
    • 注意,我们同时还获取了各个专题的热门和最新加入链接。这就是我们一开始爬取的专题链接的来源了。

    最后轻松一下,完成推荐文章爬取

    • 我们来到简书推荐页面http://jianshu.com/recommendations/notes,也就是新上榜,发现最下有个button,我们可以使用类似于首页热门的爬取方式。代码基本相似,就不做注释了。
    import requests
    from bs4 import BeautifulSoup
    
    base_url = 'http://jianshu.com'
    add_url = '/recommendations/notes'
    num = 0
    
    while(True):
        try:
            first_page = requests.request('get', base_url+ add_url).content
            soup = BeautifulSoup(first_page, "lxml")
            title_list = [i.get_text() for i in soup.select(".title a")]
            for i in title_list:
                num+=1
                print(num, '  ', i)
            # print(title_list)
            try:
                # print(soup.select(".ladda-button"))
                add_url = soup.select(".ladda-button")[-1].get("data-url")
            except:
                break
        except Exception as e:
            print(e)
            break
    
    • 注意,如果直接在windows控制台输出,会出现编码错误,我在Ipython中运行,截图如下:


    相关文章

      网友评论

      • RX_AI:大神,太牛了!
        给大神点赞,👍!
        给大家推荐一款企业级别的爬虫差产品:瑞雪采集云
        推荐使用瑞雪采集云,学习简单了解java或者python就可以使用平台

        瑞雪采集云

        瑞雪采集云第一个支持java和python的企业级的开发平台。

        瑞雪采集云的特点:

        1. 支持java和python

        2. 采集效率高

        3. 分布式

        4. 任务管理机制强大

        5. 部署简单易用

        6. 自动学习机制

        7,费用低

        web2data.com
      • firewt:以前都是手动写匹配规则,看到你推荐的chrome插件,真的太好用了,很省时间,谢谢,请问还有这样的工具吗?:+1:
        treelake:客气了。暂时没什么了解。好像有一些自动化爬虫项目可以关注。
      • d0e06dadf5ab:写入到csv文件中乱码 例如:"浣犵殑鍚嶅瓧锛氭垜鏄笉鏄湪鍝噷瑙佽繃浣狅紵"
        with open() as f: # 有重新编码的 encoding='utf-8'
        请问博主怎么解决
      • 1d692bed255e:大佬,留个联系方式可以?带带小弟?
      • 流光涟影:真心可以,jupyter完美运行
        流光涟影: @Ring0 可以的
        firewt:jupyter好用吗?
      • 老羽:没有多线程,这样效率不好吧
        treelake:@老羽 使用多线程参看《Python进程、线程、回调与协程 总结笔记》,不难处理,但是推荐使用协程。至于效率,后面几篇讲的是使用基于twisted的scrapy通用爬虫框架,效率上还是可以的
      • 孑然自安:你用的是Python3?
        treelake:@孑然自安 哪个方法对不上呢,库的方法应该没有问题的
        孑然自安:@treelake 方法都对不上,文章顶部写明一下版本吧,爬了好久坑才发现不对劲
        treelake:@孑然自安 对的,Python2的话编码可能会有些区别
      • 石诚:改天自己实践一遍再来打卡😀
      • ripperhe:大神 求带
        treelake:@ripperhe 。。。不是大神,一起加油吧

      本文标题:新手向爬虫(一)利用工具轻松爬取简书并分析

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