• Python爬虫遇上动态加载



      我想大家在使用爬虫爬取数据的过程中遇到过如下的情况吧,明明在网页源码看得到需要的内容,而且各种节点也没问题,可是就是爬取不到想要的数据,这其实就是现在大多数网页使用动态渲染(JavaScript),这种页面不再是加载后立即下载页面全部内容,许多网页在浏览器中展示的内容可能不会出现在 HTML 源代码中。这就是我们遇到上述情况的原因。

      我们在这里介绍两种解决动态加载的方法,一种是JavaScript 逆向工程,另一种是渲染 JavaScript。

    1.通过示例认识动态加载

      首先,让我们看看什么样的是动态加载。示例使用网址 http://example.python-scraping.com/search,比如我们查找A开头的国家,如下:

    在这里插入图片描述
      我们通过开发者工具查看源码,可以看到我们需要的内容在id=results节点下:

    在这里插入图片描述
      接着,我们编写提取数据的代码,代码如下:

    import re
    import requests
    from bs4 import BeautifulSoup
    import lxml.html
    import time
    from lxml.html import fromstring 
    
    #获取网页内容
    
    def download(url,user_agent='wswp',proxy=None,num_retries=2):
        print ('Downloading:',url)
        headers = {'User-Agent': user_agent} 
        try: 
            resp = requests.get(url, headers=headers, proxies=proxy) 
            html = resp.text
            if resp.status_code >= 400: 
                print('Download error:', resp.text) 
                html = None 
                if num_retries and 500 <= resp.status_code < 600: 
                    # recursively retry 5xx HTTP errors 
                    return download(url, num_retries - 1)
        
        except requests.exceptions.RequestException as e: 
            
            print('Download error:', e.reason) 
            html = None
        return html
    
    # 提取需要的内容
    html = download('http://example.python-scraping.com/search') 
    tree = fromstring(html) 
    tree.cssselect('div#results a')
    
    # 输出结果
    Downloading: http://example.python-scraping.com/search
    []
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36

      从结果看得出,我们并没有获取到我们想要的东西,这是为啥呢,这就是动态加载,打开代码下载的html源码,是找不到我们需要的东西的。结果如下:
    在这里插入图片描述

      看到这里,估计大家对动态渲染有了一定的认识,下面我们介绍如何获取这种页面内容。

    2.JavaScript 逆向工程

      我们使用之前的抓取方法,无法抓取到动态加载的页面数据,那想要抓取这部分数据,我们就得了解这种页面是如何加载数据的,该过程就描述为逆向工程。

      继续前面的例子,我们在浏览器工具中单击 Network选项卡,然后执行一次搜索,我们将会看到对于给定页面的所有请求,大多数请求都是图片,其中有个search.json的文件,单击我们发现里面包含我们需要的所有数据,如下图:

    在这里插入图片描述
      很容易发现,上图的结果其实是一个json响应,这个东西不仅可以在浏览器中访问,还可以直接下载,响应代码如下:

    import requests 
    resp = requests.get('http://example.python-scraping.com/places/ajax/search.json?&search_term=A&page_size=10&page=0') 
    resp.json()
    
    # 结果输出
    {'records': [{'pretty_link': '',
       'country_or_district': 'Afghanistan',
       'id': 7969417},
      {'pretty_link': '',
       'country_or_district': 'Aland Islands',
       'id': 7969418},
      {'pretty_link': '',
       'country_or_district': 'Albania',
       'id': 7969419},
      {'pretty_link': '',
       'country_or_district': 'Algeria',
       'id': 7969420},
       ...}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

      上面的代码中,我们通过requests 库的 json 方法访问了JSON响应,我们还可以下载原始字符串响应,然后使用json.load进行加载。但是上面的代码有个问题是我们只获取了包含A字母的内容,那如何获取全部的呢?当然可以解决,AJAX使用正则表达式进行匹配,所以我们只需要将url中的search_term=A换成`search_term=.就可以加载全部的数据。

      另外,因为url中page_size=10,所以一页中只有十个数据,这个参数AJAX并不会检查,所以我们可以给一个很大的数,是的所有数据一次性下载完成。

      最终的代码如下:

    from csv import DictWriter 
    import requests 
    template_url = 'http://example.python-scraping.com/places/ajax/search.json?&search_term=.&page_size=1000&page=0' 
    resp = requests.get(template_url) 
    data = resp.json() 
    records = data.get('records')
    
    with open('./countries_or_districts.csv', 'w') as countries_or_districts_file: 
        wrtr = DictWriter(countries_or_districts_file, fieldnames=records[0].keys()) 
        wrtr.writeheader() 
        wrtr.writerows(records)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

      运行上述代码,就可以在响应文件夹下得到如下的数据表:

    在这里插入图片描述

    3.渲染动态页面

      对于实际中,某一些网站我们能够快速地对 API 的方法进行逆向工程来了解它如何工作,以及如何使用它在一个请求中获取结果。但是,一些网站非常复杂,即使使用高级的浏览器工具也很难理解。这类网站难以实施逆向工程。这就得用到我们这里要讲解到的东西----渲染动态页面。

      尽管经过足够的努力,任何网站都可以被逆向工程,不过我们可以使用浏览器渲染引擎避免这些工作,这种渲染引擎是浏览器在显示网页时解析HTML、应用 CSS 样式并执行 JavaScript 语句的部分。在本节中,我们将使用QWebEngineView渲染引擎,通过 Qt 框架可以获得该引擎的一个便捷 Python 接口。

      我们可以使用位于http://example.python-scraping.com/dynamic 上的这个简单示例,来演示使用Qt渲染。

    常规提取:

    import lxml.html 
    
    url = 'http://example.python-scraping.com/dynamic' 
    html = download(url) 
    tree = lxml.html.fromstring(html) 
    tree.cssselect('#result')[0].text_content()
    
    # 输出:
    Downloading: http://example.python-scraping.com/dynamic
    ''
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    使用Qt框架:

    import sys
    from PyQt5.QtCore import *
    from PyQt5.QtWidgets import *
    from PyQt5.QtWebEngineWidgets import QWebEngineView
    import lxml.html
    
    
    class Render(QWebEngineView):  # 子类Render继承父类QWebEngineView
        def __init__(self, url):
            self.html = ''
            self.app = QApplication(sys.argv)
            super().__init__()
            self.loadFinished.connect(self._loadFinished)
            self.load(QUrl(url))
            self.app.exec_()
    
        def _loadFinished(self):
            self.page().toHtml(self.callable)
    
        def callable(self, data):
            self.html = data
            self.app.quit()
    
    
    if __name__ == '__main__':
        url = 'http://example.python-scraping.com/dynamic'
        r = Render(url)
        result = r.html
        tree = lxml.html.fromstring(result)
        a = tree.cssselect('#result')[0].text_content()
        print(a)
    
    # 结果输出
    Hello World
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34

      这里得说明一下,使用Jupyter Notebook会出现ImportError: QtWebEngineWidgets must be imported before a QCoreApplication instance is created的错误,所以我这里是使用PyCharm演示的,关于上述代码,说明如下:

    • class Render(QWebEngineView):构建Render类,继承自QWebEngineView
    • self.app = QApplication(sys.argv):初始化QApplication对象
    • self.loadFinished.connect(self._loadFinished):指定加载完成后的操作。这里绑定自定义函数_loadFinished
    • self.page().toHtml(self.callable):这是通过回调函数返回html。没有回调函数会报错
    • callable(self, data):回调函数,用于把html返回给外部

    4.更加自动化的渲染----Selenium

      使用前面小节中的 QWebEngineView,我们可以自定义浏览器渲染引擎,这样就能完全控制想要执行的行为。如果不需要这么高的灵活性,那么还有一个不错的更容易安装的替代品 Selenium 可以选择,它提供的 API 接口可以自动化处理多个常见浏览器。

    1.驱动下载与设置

      Selenium 就是模仿人操作浏览器,它可操作的浏览器有多种,比如Firefox (FirefoxDriver)、IE (InternetExplorerDriver)、Opera (OperaDriver) 和 Chrome (ChromeDriver)等,除此之外,它还支持 Android (AndroidDriver)和 iPhone (IPhoneDriver) 的移动应用测试,而且还包括一个基于 HtmlUnit 的无界面实现。本文只讲谷歌驱动,其他有需要自行百度。

      因为它是操作浏览器,所以需要下载相应浏览器的驱动,而且,版本也得相同。比如:我现在使用的谷歌浏览器版本是 版本 91.0.4472.124,所以我下载的驱动也必须是91版本开头的驱动,否则无法使用。
     谷歌浏览器驱动下载地址:http://npm.taobao.org/mirrors/chromedriver/

     下载好驱动后,解压,把解压文件放置在Anaconda\Scripts 目录下,当然如果你用的不是Anaconda,可以放到相应的位置。说白了,这一步就是把驱动放置在系统环境变量的path路径下,完全可以直接把驱动所在的地址添加在环境变量path中。

    添加环境变量
    在这里插入图片描述

    2. 小示例了解Selenium

    from selenium import webdriver
    driver = webdriver.Chrome()
    # 传入url
    driver.get("http://www.baidu.com")
    # 传递参数,让百度搜索Selenium2
    driver.find_element_by_id("kw").send_keys("Selenium2")
    # 找到百度一下按钮,点击一下
    driver.find_element_by_id("su").click()
    # 等待10秒
    time.sleep(10)
    # 关闭浏览器
    driver.quit()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

      到这里算是了解也体验了selenium的运行机制,下次学习如何定位元素,如何传递参数。

    3.了解Selenium的定位

      既然要模拟人操作浏览器,那总得知道输入什么,点哪里?这种问题就是它的定位,selenium有多种定位方式,分别是元素定位、Xpath定位和Css定位,每种方式都有自己的特点,实际使用时,可以多种方式混合使用。

    • 元素的定位类似于找人,可以通过人本身的属性查找,例如姓名、身份证号、性别等等,这些类似于web页面的id、name、class name等;
    • 除了上述的找人方法还可以有通过地址找,比如某国、某市、某路、某小区,这种方式类似于web页面的XPath和css方法。
    • 还有一种方法就是通过相关的人找,比如我要找小明,但是没有他的电话号码,但是我有小明爸爸的,我就可以通过小明爸爸找到小明的联系方式,继而找到小明,这种方法也可以在XPath和CSS中找到

      篇幅问题,则合理不再细讲相应的操作,网上一搜一大堆,感兴趣的自行百度。不过下面有个我当时学习的Jupyter 文件,供大家参考。

    Selenium 入门 Jupyter演示

  • 相关阅读:
    如何批量给视频添加logo水印?
    复制构造函数
    LeetCode 每日一题 2023/10/2-2023/10/8
    Python中json的用法
    ORA-00600: internal error code, arguments
    Adult Data Set
    差值结构表达的吸引能
    什么是graphQL
    [隐私计算学习笔记]4——SecretFlow与SecretNote的安装部署
    涨粉超100万,这些博主的内容密码是什么?
  • 原文地址:https://blog.csdn.net/Itsme_MrJJ/article/details/126033892