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()
    

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

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

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

  • 相关阅读:
    [转]MYSQL5.7版本sql_mode=only_full_group_by问题
    [坑]Linux MySQL环境表名默认区分大小写
    [转]CentOS 7.3 安装MySQL
    [转]Oracle截取字符串相关函数
    服务相关
    CSRF攻击
    sqlalchemy——多表操作
    sqlalchemy——基本操作
    高可用——网站运行监控
    高可用——软件质量保证
  • 原文地址:https://www.cnblogs.com/turing09/p/13171858.html
Copyright © 2011-2022 走看看