美文网首页一步一步,爬着学Python
静态网页爬虫实例1-简书搜索关键词制作词云

静态网页爬虫实例1-简书搜索关键词制作词云

作者: SyPy | 来源:发表于2017-08-22 22:31 被阅读40次

    本文为《爬着学Python》系列第八篇文章。也是第一篇爬虫实例。旨在抛砖引玉先了解一下学习爬虫可以深入的有哪些知识。


    结果

    先上结果当我们谈论卡佛时我们在谈论什么-从简书文章说起

    标题词云

    代码

    废话也先少说先上代码(请随意使用)

    # -*- coding: utf-8 -*-
    """
    Visual Studio Code
    @date: Created on Sat Aug 19 09:32:29 2017
    @author: SyPy
    search instance:http://www.jianshu.com/search?q=
    %E9%9B%B7%E8%92%99%E5%BE%B7%20%E5%8D%A1%E4%BD%9B&page=1&type=note
    article instance:http://www.jianshu.com/p/7c917833c8bd
    """
    
    import json
    import jieba
    import numpy as np
    import requests as rqs
    import PIL.Image as PIM
    from bs4 import BeautifulSoup as BS
    from wordcloud import WordCloud as WC
    
    
    def get_links():
        """
        进行搜索并提取出文章链接信息,
        返回列表
        """
        def get_page():
            """
            获取各页搜索的请求地址,
            返回列表
            """
            return [SEARCH_URL + '&page=%s' %page for page in range(1, SEARCH_PAGES_COUNT + 1)]
    
        def get_response(search_url):
            """
            获取搜索页面结果的json数据,
            返回字典
            """
            return json.loads(rqs.get(search_url).content)
    
        def transfer_response(response_dict):
            """
            提取出将字典中和文章链接有关的信息,
            返回列表
            """
            return [DOMAIN_URL + '/' + entry['slug'] for entry in response_dict['entries']]
    
        def process_page(search_url):
            """
            对页内操作进行整合,
            返回列表
            """
            return transfer_response(get_response(search_url))
    
        return list(map(process_page, get_page()))
    
    def flattern_urls(article_urls):
        """
        将多维列表拆开
        """
        for each in article_urls:
            if not isinstance(each, list) or isinstance(each, str):
                yield each
            else:
                yield from flattern_urls(each)
    
    def get_articles(article_urls):
        """
        只要做词云,所以直接获取文章标题和内容保存,
        标题collect_title,
        正文collect_text
        """
        r_soups = []
        for article_url in article_urls:
            print(article_url)
            try:
                r_soup = BS(rqs.get(article_url, timeout=5).text, 'lxml')
            except:
                print('failed to open article site{}'.format(article_url))
                r_soup = BS("<h1 class='show-content'></h1>", 'lxml')
            r_soups += r_soup
        article_titles, article_texts = [[], []]
        for r_soup in r_soups:
            try:
                article_titles.append(str(r_soup.select('h1')[0].string))
                for paragraph in r_soup.select('.show-content')[0].select('p'):
                    article_texts.append(str(paragraph))
            except AttributeError:
                continue
    
        return [article_titles, article_texts]
    
    def process_articles(source):
        """
        删除标题和正文列表中的标签,
        内容简单,不用正则直接换即可
        """
        for article in source:
            article = str(article)
            article.replace('<div class="show-content" data-note-content="">', '')
            article.replace('</div>', '')
            article.replace('<p>', '')
            article.replace('</p>', '')
            article.replace('<h1 class="title">', '')
            article.replace('</h1>', '')
    
    def produce_cloud(source, save_dir, process_cata):
        """
        制作文字云,保存在本地
        """
        source_text = ''.join(source)
        wordlist_jieba = jieba.cut(source_text, cut_all=True)
        wl_space_split = ' '.join(wordlist_jieba)
    
        img_bed = np.array(PIM.open(save_dir + 'imgbed.jpg'))
        gen_cloud = WC(background_color="white", max_words=2000,
                       mask=img_bed,
                       max_font_size=40, random_state=42,
                       font_path=save_dir + 'FZCSJW.TTF').generate(wl_space_split)
        gen_cloud.to_file(save_dir + process_cata + 'cloud.jpg')
    
    if __name__ == '__main__':
        #可配置参数
        SEARCH_NAME = '%E9%9B%B7%E8%92%99%E5%BE%B7%20%E5%8D%A1%E4%BD%9B' #搜索关键字:雷蒙德 卡佛
        SEARCH_PAGES_COUNT = 30                                          #搜索页数
        LOCAL_DIR = 'G:\\action\\searchcarver\\'                           #文件本地保存地址
        #准备
        DOMAIN_URL = 'http://www.jianshu.com/p'
        SEARCH_URL = 'http://www.jianshu.com/search/do?q=' + SEARCH_NAME
        #获取链接
        ARTICLE_URLS_GROUP = get_links()
        ARTICLE_URLS = list(flattern_urls(ARTICLE_URLS_GROUP))
        print('{} links collected'.format(len(ARTICLE_URLS)))
        #获取文章标题和正文
        TITLE_LIST, TEXT_LIST = get_articles(ARTICLE_URLS)
        print('articles collected')
        print(TITLE_LIST[:20], type(TITLE_LIST[0]))
        #文本处理,生成文字云并保存
        process_articles(TITLE_LIST)
        process_articles(TEXT_LIST)
        produce_cloud(TITLE_LIST, LOCAL_DIR, 'title')
        produce_cloud(TEXT_LIST, LOCAL_DIR, 'text')
    
    

    参数和函数说明

    SEARCH_NAME是搜索的关键字,例如示例代码中的一长串就是中文‘雷蒙德 卡佛’,关于编码的相关知识会在以后进行说明,现在先给一个链接供不了解但好奇心较强的同学先解解渴The Absolute Minimum Every Software Developer Absolutely, Positively Must Know About Unicode and Character Sets (No Excuses!),作者是Stackoverflow的CEO,大佬的文章标题就是这么长不怪我。没什么兴趣的只需要直接在简书搜索你要的关键词,注意浏览器中地址的变化http://www.jianshu.com/search?q=%E9%9B%B7%E8%92%99%E5%BE%B7%20%E5%8D%A1%E4%BD%9B&page=1&type=note你复制粘贴就能得到这一长串乱码一样的东西了,事实上你在Python3的爬虫中requests请求地址有中文也是会自动编码的,也就是说http://www.jianshu.com/search?q=雷蒙德%20卡佛&page=1&type=note这样的地址并不妨碍你得到正常的响应。但我要说的是,如果是初学爬虫,要学会观察浏览器地址栏,这深入的话会涉及到web路由以及建站方案设计等,这就是为什么我们计划中会把Flask放进学习内容中,因为,仅仅是地址栏的信息是不够的,事实上我获取搜索结果的地址来自于浏览器的开发者工具,绝大多数浏览器F12即可快捷进入,我们需要通过其中的“Network”或者“网络”模块来查看网络请求信息,更多的应用或者程序中的网络请求我们还会用到“Fiddler”这样的抓包工具。看,只是一个地址,就有说不完的知识。计算机网络,这也是以后会介绍的内容。

    SEARCH_PAGES_COUNT这个参数是搜索页数,因为我们手动查看可以看出来,搜索结果靠后的文章与搜索的关键字可能关系不大了。当然我们可以用语义分析去主动判断关联度,但是可以这个工作看到简书自己做得都很差,所以,这样的内容并不在本专题的计划内容范围内。当然,有兴趣的可以实现了以后往简书投简历:)

    get_links是发出请求获取搜索结果,并且提取出具体文章的链接信息的函数。最后的返回值是一个包含几百个链接的列表。这样做其实是很笨重的,因为如果返回的数据量特别大,用列表存储显然就不明智了,我们理想的请求函数是一个生成器函数

    其他函数也不多说了,代码中相应地方都用中文写了一些注释。它们不是今天要讲的重点。

    正文

    代码注释已经写得比较全了。尤其是请求搜索结果的函数,因为既然是第一次实例,主要讲一下请求数据思路。为了将步骤分离,展示数据变化的过程,写成了类似函数式编程的形式。后面会写成相对“正常”的形式。毕竟能把Python代码写成现在这么丑也是需要动一番脑子的。

    我们认识爬虫,一般可以认为是用爬虫程序代替可视化浏览器进行网络访问,并且发挥程序的优点——客制化的需求,方便的处理数据、高效严谨的运行。

    请求数据的思路

    • 需要什么样的数据,本例中是搜索结果各文章的链接
    • 发出请求,获取响应的数据
    • 分析返回数据的结构,清洗初始数据
    • 将清洗过的数据转化成下一步需要用到的链接

    函数实现

    像上面贴出来的代码中每一步都写清楚了做的事情。这里我们重新写一个一般形式的获取下载地址的函数。

    def get_links():
        for page in range(1, 3):
            tmp_url = SEARCH_URL + '&page=%s' %page
            response = rqs.get(tmp_url)
            r_dict = json.loads(response.content)
            r_entries = r_dict['entries']
            for r_entry in r_entries:
                yield DOMAIN_URL + '/' + r_entry['slug']
    

    这个重写的get_links函数可以直接替代进之前贴出来的代码完成相同的功能。从这两种形式我们也可以看到函数式编程非常优雅清晰,但是不如面向过程编程直白朴素。我们可以在这个函数中随意插入print函数来进行反馈,前者做不到的。

    另外其中获取请求response的部分可以另写请求函数来满足各种需求(响应延迟,修改header,异常处理等)。甚至说我们可以创建下载器对象来使其更加健壮,将所有下载相关任务进行整合,但是这就属于比较有技巧的部分了,以后涉及到scrapy框架我们可以看到类似的想法是如何实现的,这里也不深究了。

    总结

    是的,我只贴了两段代码,并没有进行过多解释。一方面,单纯借代码尝试一下的复制粘贴改一两个参数就可以了,并不需要解释。另一方面有学习相关知识需求的,我建议还是踏踏实实慢慢学,像上面这种简单的爬虫说实话没有太大的实际意义,仅仅算是个小玩具而已。现在这个爬虫完全可以做成API部署在服务器上,需要改进和优化的地方数不胜数,对于具有实际意义来说还是任重道远。我们自然也可以借用爬虫熟悉Python编程,这也是我的初衷,不过这也要一步一步来不是吗。

    链接

    1. The Absolute Minimum Every Software Developer Absolutely, Positively Must Know About Unicode and Character Sets (No Excuses!)
    2. 爬虫 - 汪海的实验室 - CSDN博客
    3. amueller/word_cloud: A little word cloud generator in Python

    相关文章

      网友评论

        本文标题:静态网页爬虫实例1-简书搜索关键词制作词云

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