zoukankan      html  css  js  c++  java
  • BeautifulSoup爬取网页分页

    在前面我们介绍了如何通过某个页面爬取与之关联的外部网页,当时介绍的是使用广度优先搜索的方式爬取。

    在本节,我们将介绍另一种爬取外部链接的方式,即深度优先搜索,爬取网页的分页。

    由于本人喜欢古诗词,今天爬取的网页的内容就是古诗词,爬取的链接为:https://so.gushiwen.org/shiwen/。

    如下图所示:

    在同一个网页,内容是通过分页的形式进行展示,今天介绍如何爬取分页。

    一、思路分析

    我们知道,对于网页的分页内容访问,我们通常可以通过点击“上一页”或者“下一页”按钮访问关联的页面。

    因此,在爬取分页内容的时候,我们可以采用这一思路,进行深度递归,即可访问所有的分页内容。

    当然,事实上,由于软件工程师在编码过程中会对不同分页的url做一定的映射,因此除了以上思路,我们可以选择另外一种解决办法就是分析这种映射关系是什么。

    如果能确定映射关系,那么就不需要进行深度递归,毕竟操作系统对于编程语言的栈的深度是有限制的,比如python最多递归1000次,超过1000次将报栈溢出的错误。

    在接下来的示例演示中将对两种方法进行介绍。

    二、示例演示

    1.深度递归访问分页内容

    正如之前一直强调的,在使用网络爬虫进行网页爬取的时候,第一步总是先打开目标网页,然后在开发者模式下分析网页的特点,然后根据分析的结果进行爬取。

    打开目标网页的结构如下图所示:

    在网页中搜索“下一页”按钮所在的标签位置,我们可以看到如上的结果。并且通过认真观察,我们能够看见“下一页”所在的标签是一个超链接,因此我们可以猜测,这个链接就是用来从当前页跳转到下一页。

    为了验证我们的猜想是否正确,我们可以直接点开“下一页”按钮的跳转页面是否与我们刚才观察的url一致。

    打开“下一页”的页面,我们可以看到url如下:

    显然,两个页面是存在联系的。浏览器中的url和超链接的url除了隔了域名https://so.gushiwen.org/不一样之外,后半部分都是一致的。

    因此证实了我们的猜想是正确的,因此我们只需要获取页面中“下一页”中超链接的url,再拼接上域名,就能得到下一页页面的url。通过递归操作,我们能够爬取所有的页面。

    这里定义一个函数进行实现,使用一个队列存储每次获取的url:

    def getNextUrl(url,que):
        '''
    
        :return:
        '''
        if url==None or len(url)==0:
            return
        try:
            html = requests.get(url)
            html.encoding = None
            new_url = bs.findAll("a", {"class": "amore"})[0].attrs['href']
            new_url= domain_prefix+new_url
            que.put(new_url)
            getNextUrl(new_url,que)
        except Exception as e:
            print("get attr error!")
    

    在找到不同分页页面间的关联关系之后,接下来就是找到我们的目标内容:古诗词所在的位置。通过我们前面介绍的知识,我们很容易找到我们的目标内容:

    利用我们上一章的讲过的标签定位方法,很容易就可以获取到目标内容。

    为了方便查看,我们把每首诗的内容使用文件进行存储,代码实现如下:

    def get_poetry(bs):
        '''
        获取每首诗的内容
    
        :param url:
        :return:
        '''
    
        # 将所有诗词内容写入文本
        poetry_set = bs.findAll("div", {"class": "sons"})
        for poetry in poetry_set:
            titles = poetry.findAll('b')
            if len(titles)==0:
                break
    
            title=titles[0].text.split('/')[-1].strip()
            res=''+title
            with open('./book/'+title+".txt", 'w') as fo:
                info = poetry.findAll('p', {'class': 'source'})
                for i in info:
                    fo.write(i.get_text())
                    res+=','+i.get_text()
    
                content = poetry.findAll('div', {'class': 'contson'})
                for i in content:
                    fo.write(i.get_text())
                    res += ','+i.get_text()
    

    为了提高执行效率,我们可以把页面内容提取和url访问分开,使用多线程进行并行操作。同时,要考虑到访问过程中会存在访问出错的情况,因此需要捕获异常。

    最终得到的完整代码如下:

    # 请求库
    import requests
    # 解析库
    from bs4 import BeautifulSoup
    from queue import Queue
    import threading
    import os
    import re
    
    domain_prefix=r"https://so.gushiwen.org"
    
    def get_poetry(bs):
        '''
        获取每首诗的内容
    
        :param url:
        :return:
        '''
    
        # 将所有诗词内容写入文本
        poetry_set = bs.findAll("div", {"class": "sons"})
        for poetry in poetry_set:
            titles = poetry.findAll('b')
            if len(titles)==0:
                break
    
            title=titles[0].text.split('/')[-1].strip()
            res=''+title
            with open('./book/'+title+".txt", 'w') as fo:
                info = poetry.findAll('p', {'class': 'source'})
                for i in info:
                    fo.write(i.get_text())
                    res+=','+i.get_text()
    
                content = poetry.findAll('div', {'class': 'contson'})
                for i in content:
                    fo.write(i.get_text())
                    res += ','+i.get_text()
    
    def getNextUrl(url,que):
        '''
    
        :return:
        '''
        if url==None or len(url)==0:
            return
    
        try:
            html = requests.get(url)
            html.encoding = None
            bs = BeautifulSoup(html.text, 'html.parser')
            #启动一个子线程进行内容处理
            t = threading.Thread(target=get_poetry, args=(bs,))
            t.start()
            t.join()
    
            new_url = bs.findAll("a", {"class": "amore"})[0].attrs['href']
            new_url= domain_prefix+new_url
            que.put(new_url)
            getNextUrl(new_url,que)
        except Exception as e:
            print("get attr error!")
    

    爬取的网页链接

    url=r"https://so.gushiwen.org/shiwen/"
    html=requests.get(url)
    html.encoding=None
    bs=BeautifulSoup(html.text,'html.parser')
    
    #爬取所有的网页链接
    urls=Queue()
    getNextUrl(url,urls)
    

    最终得到的结果如下:

    2.研究映射关系快速访问

    通过递归的方式获取所有的url思路很清晰,也很简单,但是效率不高。因此,为了提高效率,我们可以比较分析不同的特点,找出规律。

    摘取部分url展示如下:

    通过细细比较,我们可以很容易发现,这些url除了最后的1位或者两位数字不同之外,其他的都是相同的,并且这些数字存在一个以1为步长的线性增长关系。

    因此我们完全可以通过一个循环来生成所有的url,代码实现如下:

    #所有的网页链接
    urls=Queue()
    for i in range(1,101,1):
        tmp=url.replace("1.aspx",str(i)+".aspx")
        print(tmp)
        urls.put(tmp)
    

    在得到所有的URL之后,接下来我们只需要抓取每个页面的url的HTML页面,然后获取内容皆可。除了获取url的方式不同之外,其他抓取的方式与深度递归方式差不多,如下所示:

    def get_poetry(que):
        '''
        获取每首诗的内容
    
        :param url:
        :return:
        '''
        while not que.empty():
            url=que.get()
    
            try:
                html = requests.get(url)
                html.encoding = None
                bs = BeautifulSoup(html.text, 'html.parser')
                # 将所有诗词内容写入文本
                poetry_set = bs.findAll("div", {"class": "sons"})
                for poetry in poetry_set:
                    titles = poetry.findAll('b')
                    if len(titles)==0:
                        break
    
                    title=titles[0].text.split('/')[-1].strip()
                    print(title)
                    res=''+title
                    with open('./book/'+title+".txt", 'w') as fo:
                        info = poetry.findAll('p', {'class': 'source'})
                        for i in info:
                            fo.write(i.get_text())
                            res+=','+i.get_text()
    
                        content = poetry.findAll('div', {'class': 'contson'})
                        for i in content:
                            fo.write(i.get_text())
                            res += ','+i.get_text()
                        
            except Exception as e:
                print(url)
                print("get attr error!")
    

    因为抓取的页面之间不存在先后关系,因此可以并行执行。这里采用多线程的方式来提高抓取的效率。

    得到最终的代码如下:

    # 请求库
    import requests
    # 解析库
    from bs4 import BeautifulSoup
    from queue import Queue
    import threading
    import pyttsx3
    import os
    import re
    
    domain_prefix=r"https://so.gushiwen.org"
    
    def get_poetry(que):
        '''
        获取每首诗的内容
    
        :param url:
        :return:
        '''
        while not que.empty():
            url=que.get()
    
            try:
                html = requests.get(url)
                html.encoding = None
                bs = BeautifulSoup(html.text, 'html.parser')
                # 将所有诗词内容写入文本
                poetry_set = bs.findAll("div", {"class": "sons"})
                for poetry in poetry_set:
                    titles = poetry.findAll('b')
                    if len(titles)==0:
                        break
    
                    title=titles[0].text.split('/')[-1].strip()
                    print(title)
                    res=''+title
                    with open('./book/'+title+".txt", 'w') as fo:
                        info = poetry.findAll('p', {'class': 'source'})
                        for i in info:
                            fo.write(i.get_text())
                            res+=','+i.get_text()
    
                        content = poetry.findAll('div', {'class': 'contson'})
                        for i in content:
                            fo.write(i.get_text())
                            res += ','+i.get_text()
    
            except Exception as e:
                print(url)
                print("get attr error!")
    
    # 爬取的网页链接
    url=r"https://so.gushiwen.org/shiwen/default_2A515ea88d1858A1.aspx"
    
    #爬取所有的网页链接
    urls=Queue()
    # urls.put(url)
    for i in range(1,101,1):
        tmp=url.replace("1.aspx",str(i)+".aspx")
        print(tmp)
        urls.put(tmp)
    
    # 定义多线程
    for i in range(10):
        t = threading.Thread(target=get_poetry, args=(urls,))
        t.start()
        t.join()
    

    通过执行对比分析,我们很容易就可以发现第二种方法比第一种方法不仅代码更为简洁,效率也更高。

    当然,抓取内容只是整个数据分析的基本操作,为了对数据进行更深入的理解,我们需要借助一些数据分析的工具对数据进行处理和分析。

    由于涉及的内容较多,难度也更大,将在后续的篇章逐渐给大家进行介绍。

  • 相关阅读:
    树链剖分 (模板) 洛谷3384
    ST表 (模板) 洛谷3865
    IOI 2005 River (洛谷 3354)
    IOI 2005 River (洛谷 3354)
    poj1094 Sorting It All Out
    poj1094 Sorting It All Out
    spfa(模板)
    HAOI 2006 受欢迎的牛 (洛谷2341)
    HAOI 2006 受欢迎的牛 (洛谷2341)
    洛谷1850(NOIp2016) 换教室——期望dp
  • 原文地址:https://www.cnblogs.com/turing09/p/13171858.html
Copyright © 2011-2022 走看看