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
[]

  从结果看得出,我们并没有获取到我们想要的东西,这是为啥呢,这就是动态加载,打开代码下载的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': '<div><a href="/places/default/view/Afghanistan-1"><img src="/places/static/images/flags/af.png" /> Afghanistan</a></div>',
   'country_or_district': 'Afghanistan',
   'id': 7969417},
  {'pretty_link': '<div><a href="/places/default/view/Aland-Islands-2"><img src="/places/static/images/flags/ax.png" /> Aland Islands</a></div>',
   'country_or_district': 'Aland Islands',
   'id': 7969418},
  {'pretty_link': '<div><a href="/places/default/view/Albania-3"><img src="/places/static/images/flags/al.png" /> Albania</a></div>',
   'country_or_district': 'Albania',
   'id': 7969419},
  {'pretty_link': '<div><a href="/places/default/view/Algeria-4"><img src="/places/static/images/flags/dz.png" /> Algeria</a></div>',
   'country_or_district': 'Algeria',
   'id': 7969420},
   ...}

  上面的代码中,我们通过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)

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

在这里插入图片描述

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
''

使用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

  这里得说明一下,使用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()

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

3.了解Selenium的定位

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

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

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

Selenium 入门 Jupyter演示

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值