动态网页(通过Ajax技术异步更新网页内容)的爬虫方法总结

动态网页数据抓取

文章目录

  • 动态网页数据抓取
    • 什么是AJAX:
    • 获取ajax数据的方式:
    • Selenium+chromedriver获取动态数据:
    • 安装Selenium和chromedriver:
      • 快速入门:
      • selenium常用操作:
        • 关闭页面:
        • 定位元素:
        • 操作表单元素:
        • 行为链:
        • Cookie操作:
        • 页面等待:
        • 切换页面:
        • 设置代理ip:
        • `WebElement`元素:
    • 实战
      • selenuim实现拉勾网网页爬虫

什么是AJAX:

AJAX(Asynchronouse JavaScript And XML)异步JavaScript和XML。过在后台与服务器进行少量数据交换,Ajax 可以使网页实现异步更新。这意味着可以在不重新加载整个网页的情况下,对网页的某部分进行更新。传统的网页(不使用Ajax)如果需要更新内容,必须重载整个网页页面。因为传统的在传输数据格式方面,使用的是XML语法。因此叫做AJAX,其实现在数据交互基本上都是使用JSON。使用AJAX加载的数据,即使使用了JS,将数据渲染到了浏览器中,在右键->查看网页源代码还是不能看到通过ajax加载的数据,只能看到使用这个url加载的html代码。

获取ajax数据的方式:

  1. 直接分析ajax调用的接口。然后通过代码请求这个接口。
  2. 使用Selenium+chromedriver模拟浏览器行为获取数据。
方式 优点 缺点
分析接口 直接可以请求到数据。不需要做一些解析工作。代码量少,性能高。 分析接口比较复杂,特别是一些通过js混淆的接口,要有一定的js功底。容易被发现是爬虫。
selenium 直接模拟浏览器的行为。浏览器能请求到的,使用selenium也能请求到。爬虫更稳定。 代码量多。性能低。

Selenium+chromedriver获取动态数据:

Selenium相当于是一个机器人。可以模拟人类在浏览器上的一些行为,自动处理浏览器上的一些行为,比如点击,填充数据,删除cookie等。chromedriver是一个驱动Chrome浏览器的驱动程序,使用他才可以驱动浏览器。当然针对不同的浏览器有不同的driver。以下列出了不同浏览器及其对应的driver:

  1. Chrome:https://sites.google.com/a/chromium.org/chromedriver/downloads
  2. Firefox:https://github.com/mozilla/geckodriver/releases
  3. Edge:https://developer.microsoft.com/en-us/microsoft-edge/tools/webdriver/
  4. Safari:https://webkit.org/blog/6900/webdriver-support-in-safari-10/

安装Selenium和chromedriver:

  1. 安装Selenium,Selenium有很多语言的版本,有java、ruby、python等。我们下载python版本的就可以了。

     pip install selenium
    
  2. 安装chromedriver:下载完成后,放到不需要权限的纯英文目录下就可以了。

    注意要下载与chrome浏览器对应版本的chromedriver,比如我的是版本 81.0.4044.129,对应的driver就是http://npm.taobao.org/mirrors/chromedriver/81.0.4044.20/

快速入门:

现在以一个简单的获取百度首页的例子来讲下Seleniumchromedriver如何快速入门:

from selenium import webdriver

# chromedriver的绝对路径
driver_path = r"C:\Program Files (x86)\Google\Chrome\Application\chromedriver.exe"

# 初始化一个driver,并且指定chromedriver的路径
driver = webdriver.Chrome(executable_path=driver_path)
# 请求网页
driver.get("https://www.baidu.com/")
# 通过page_source获取网页源代码
print(driver.page_source)

selenium常用操作:

更多教程请参考:http://selenium-python.readthedocs.io/installation.html#introduction

关闭页面:

  1. driver.close():关闭当前页面。
  2. driver.quit():退出整个浏览器。

定位元素:

  1. find_element_by_id
    

    :根据id来查找某个元素。等价于:

     submitTag = driver.find_element_by_id('su')
     submitTag1 = driver.find_element(By.ID,'su')
    
  2. find_element_by_class_name
    

    :根据类名查找元素。 等价于:

     submitTag = driver.find_element_by_class_name('su')
     submitTag1 = driver.find_element(By.CLASS_NAME,'su')
    
  3. find_element_by_name
    

    :根据name属性的值来查找元素。等价于:

     submitTag = driver.find_element_by_name('email')
     submitTag1 = driver.find_element(By.NAME,'email')
    
  4. find_element_by_tag_name
    

    :根据标签名来查找元素。等价于:

     submitTag = driver.find_element_by_tag_name('div')
     submitTag1 = driver.find_element(By.TAG_NAME,'div')
    
  5. find_element_by_xpath
    

    :根据xpath语法来获取元素。等价于:

     submitTag = driver.find_element_by_xpath('//div')
     submitTag1 = driver.find_element(By.XPATH,'//div')
    
  6. find_element_by_css_selector:根据css选择器选择元素。等价于:

     submitTag = driver.find_element_by_css_selector('//div')
     submitTag1 = driver.find_element(By.CSS_SELECTOR,'//div')
    

    要注意,find_element是获取第一个满足条件的元素。find_elements是获取所有满足条件的元素。

import time

from selenium import webdriver

driver_path = r"C:\Program Files (x86)\Google\Chrome\Application\chromedriver.exe"
driver  = webdriver.Chrome(executable_path=driver_path)
# driver.get('http://baidu.com/')
"""选中一个元素"""
# input_tag = driver.find_element_by_id('kw')
# input_tag = driver.find_element_by_name('wd')
# input_tag = driver.find_element_by_class_name('s_ipt')
# input_tag = driver.find_element_by_xpath('//input[@id="kw"]')
# input_tag = driver.find_element_by_css_selector(".s_ipt")

# input_tag.send_keys('python') # 往百度的搜索框里填入python
"""选中多个元素"""
driver.get("https://www.qq.com/")
navagations = driver.find_elements_by_class_name("nav-item")
for x in navagations:
    print(x.text)
    if x.text=="NBA": # 如果是NBA的导航栏,就点击它
        x.click()
        break

# time.sleep(2)
# driver.quit()

如果只是想解析网页中的数据,那么推荐将网页源代码扔给lxml来解析,因为lxml的底层使用的是c语言,所以解析效率会更高

如果想要对元素进行进一步的操作,比如点击事件,输入文本框内容时就必须使用selenium给我们提供的查找元素的方法,因为它返回的元素对象内部封装了很多方法可以供我们进行操作,比如click

操作表单元素:

要操作表单元素,首先需要知道一个表单中包含哪些常用的元素,这需要一些html语法的基础,可以参见w3cschoolhttps://www.w3school.com.cn/html/html_forms.asp

  • 常用的表单元素:
    input [type=‘text/password/email/number’ ‘submit’] 输入框
    button:input[type = ‘submit’] 一般是提交按钮
    checkbox:input[type = ‘checkbox’] 勾选,比如记住密码等选项
    select : 下拉列表
  1. 操作输入框:分为两步。第一步:找到这个元素。第二步:使用send_keys(value),将数据填充进去。示例代码如下:

     inputTag = driver.find_element_by_id('kw')
     inputTag.send_keys('python')
    

    使用clear方法可以清除输入框中的内容。示例代码如下:

     inputTag.clear()
    
  2. 操作checkbox:因为要选中checkbox标签,在网页中是通过鼠标点击的。因此想要选中checkbox标签,那么先选中这个标签,然后执行click事件。示例代码如下:

    driver.get("https://www.douban.com/")
    driver.switch_to.frame(driver.find_element_by_xpath("//iframe[contains(@style,'height: 300px; width: 300px;')]")) # switch_to.frame函数解决无法选中元素的问题
    rememberTag = driver.find_element_by_name("remember")
    rememberTag.click() # 选中记得密码的checkbox
    rememberTag.click() # 点两次取消选中
    
  3. 选择select:select元素不能直接点击。因为点击后还需要选中元素。这时候selenium就专门为select标签提供了一个类selenium.webdriver.support.ui.Select。将获取到的元素当成参数传到这个类中,创建这个对象。以后就可以使用这个对象进行选择了。示例代码如下:

    
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>下拉框练习title>
    head>
    <body>
    <select name="xqj" id="1">
        <option value="01">破阵子·为陈同甫赋壮词以寄之option>
        <option value="02">醉里挑灯看剑,梦回吹角连营。option>
        <option value="03">八百里分麾下炙,五十弦翻塞外声。沙场秋点兵。option>
        <option value="04">马作的卢飞快,弓如霹雳弦惊。option>
        <option value="05">了却君王天下事,赢得生前身后名。option>
        <option value="06">可怜白发生!option>
    select>
    <select name="lb" id="2">
        <option value="01">静夜思option>
        <option value="02">床前明月光option>
        <option value="03">疑是地上霜option>
        <option value="04">举头望明月option>
        <option value="05">低头思故乡option>
    select>
    body>
    html>
    
    from selenium import webdriver
    from selenium.webdriver.support.ui import Select
    driver.get(r'D:\Python_Code\net_scrapy\爬虫进阶\动态网页爬虫\select.html')
    select_btn = Select(driver.find_element_by_name("xqj")) # 在操作select标签时需要将获取的标签对象传入Select对象
    select_btn.select_by_index(1) # 选择第二个选项
    select_btn.select_by_value("03") # 根据选项option标签的value属性来选择
    select_btn.select_by_visible_text("可怜白发生!") # 根据option标签下的文本内容来选择
    select_btn.deselect_all() # 取消所有选中
    
  4. 操作按钮:操作按钮有很多种方式。比如单击、右击、双击等。这里讲一个最常用的。就是点击。直接调用click函数就可以了。示例代码如下:

    driver.get("http://www.baidu.com")
    input_tag = driver.find_element_by_id('kw')
    input_tag.send_keys('python 爬虫')
    submit_tag = driver.find_element_by_id("su")
    submit_tag.click()
    

行为链:

有时候在页面中的操作可能要有很多步,那么这时候可以使用鼠标行为链类ActionChains来完成。比如现在要将鼠标移动到某个元素上并执行点击事件。那么示例代码如下:

from selenium import webdriver
from selenium.webdriver.common.action_chains import ActionChains

driver_path = r"C:\Program Files (x86)\Google\Chrome\Application\chromedriver.exe"
driver = webdriver.Chrome(executable_path=driver_path) # 创建谷歌浏览器的driver对象
driver.get("http://www.baidu.com/")

input_tag = driver.find_element_by_id('kw')
submit_tag = driver.find_element_by_id('su')

actions = ActionChains(driver) # 先用ActionChains包裹driver
actions.move_to_element(input_tag)
actions.send_keys_to_element(input_tag,'python 爬虫')
actions.move_to_element(submit_tag)
actions.click(submit_tag)
actions.perform()

还有更多的鼠标相关的操作。

  • click_and_hold(element):点击但不松开鼠标。
  • context_click(element):右键点击。
  • double_click(element):双击。 更多方法请参考:http://selenium-python.readthedocs.io/api.html

Cookie操作:

  1. 获取所有的cookie:

     for cookie in driver.get_cookies():
         print(cookie)
    
   
2. 根据cookie的key获取value:

   ```python
    value = driver.get_cookie(key)
  1. 删除所有的cookie:

     driver.delete_all_cookies()
    
  2. 删除某个cookie:

     driver.delete_cookie(key)
    

页面等待:

现在的网页越来越多采用了 Ajax 技术,这样程序便不能确定何时某个元素完全加载出来了。如果实际页面等待时间过长导致某个dom元素还没出来,但是你的代码直接使用了这个WebElement,那么就会抛出NullPointer的异常。为了解决这个问题。所以 Selenium 提供了两种等待方式:一种是隐式等待、一种是显式等待。

  1. 隐式等待:调用driver.implicitly_wait。那么在获取不可用的元素之前,会先等待10秒中的时间。示例代码如下:

    driver = webdriver.Chrome(executable_path=driver_path)
    driver.implicitly_wait(10)
    # 请求网页
    driver.get("https://www.douban.com/")
    
  2. 显示等待:显示等待是表明某个条件成立后才执行获取元素的操作。也可以在等待的时候指定一个最大的时间,如果超过这个时间那么就抛出一个异常。显示等待应该使用selenium.webdriver.support.excepted_conditions期望的条件和selenium.webdriver.support.ui.WebDriverWait来配合完成。示例代码如下:

     from selenium import webdriver
     from selenium.webdriver.common.by import By
     from selenium.webdriver.support.ui import WebDriverWait
     from selenium.webdriver.support import expected_conditions as EC
    
     driver = webdriver.Firefox()
     driver.get("http://www.douban.com/")
     try:
         #显式等待5秒,如果5秒内任意时刻要获取的元素出现了,就结束等待
         element = WebDriverWait(driver, 5).until(
             EC.presence_of_element_located((By.CLASS_NAME,'lnk-book'))
         )
         element.click()
     finally:
         driver.quit()
    

    一些其他的等待条件:

    • presence_of_element_located:某个元素已经加载完毕了。

    • presence_of_all_emement_located:网页中所有满足条件的元素都加载完毕了。

    • element_to_be_cliable:某个元素是可以点击了。

      更多条件请参考:http://selenium-python.readthedocs.io/waits.html

切换页面:

有时候窗口中有很多子tab页面。这时候肯定是需要进行切换的。selenium提供了一个叫做switch_to_window来进行切换,具体切换到哪个页面,可以从driver.window_handles中找到。

示例代码如下:

  • 打开多个网页

    # 两个get函数同时使用并不能打开多个tab页面,而是在第一个页面还没加载完的时候就在当前页面重定向到了下一个页面
    driver.get("http://www.baidu.com/")
    # driver.get("http://www.douban.com/")
    
    driver.execute_script("window.open('http://www.douban.com')")  # 传入的是一个javascript的脚本
    
  • 页面的切换

    # 切换到这个新的页面中
    self.driver.switch_to_window(self.driver.window_handles[1])
    

设置代理ip:

有时候频繁爬取一些网页。服务器发现你是爬虫后会封掉你的ip地址。这时候我们可以更改代理ip。更改代理ip,不同的浏览器有不同的实现方式。这里以Chrome浏览器为例来讲解:

from selenium import webdriver

options = webdriver.ChromeOptions()
options.add_argument("--proxy-server=http://110.73.2.248:8123") # 设置代理选项
driver_path = r"D:\ProgramApp\chromedriver\chromedriver.exe"
driver = webdriver.Chrome(executable_path=driver_path,chrome_options=options)

driver.get('http://httpbin.org/ip')
print(driver.page_source)

WebElement元素:

from selenium.webdriver.remote.webelement import WebElement类是每个获取出来的元素的所属类。
有一些常用的属性:

  1. get_attribute:获取这个标签的某个属性的值。
  2. screentshot:获取当前页面的截图。这个方法只能在driver上使用。
    driver的对象类,也是继承自WebElement
    更多请阅读相关源代码。

例子:

from selenium import webdriver
from selenium.webdriver.remote.webelement import WebElement

driver_path = r"C:\Program Files (x86)\Google\Chrome\Application\chromedriver.exe"
driver = webdriver.Chrome(executable_path=driver_path)

driver.get("http://baidu.com")
submit_btn = driver.find_element_by_id('su')
print(type(submit_btn))
print(submit_btn.get_attribute("value"))
driver.save_screenshot('baidu.png') # 截图

实战

selenuim实现拉勾网网页爬虫

传统版需要代理才能绕过拉勾网的反爬虫机制,而且需要找对其网页的Ajax数据请求的url

import requests
from lxml import etree
import time
import re
HEADERS = {
    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.129 Safari/537.36',
    'referer': 'https://www.lagou.com/jobs/list_python/p-city_0?&cl=false&fromSearch=true&labelWords=&suginput=',
    'cookie': 'user_trace_token=20200424213435-50618282-9133-4211-9076-521ad6bd97f1; sensorsdata2015jssdkcross=%7B%22distinct_id%22%3A%22171ac65afbe5f3-0311f28b14fe96-7373667-1327104-171ac65afbf913%22%2C%22%24device_id%22%3A%22171ac65afbe5f3-0311f28b14fe96-7373667-1327104-171ac65afbf913%22%2C%22props%22%3A%7B%22%24latest_traffic_source_type%22%3A%22%E7%9B%B4%E6%8E%A5%E6%B5%81%E9%87%8F%22%2C%22%24latest_referrer%22%3A%22%22%2C%22%24latest_referrer_host%22%3A%22%22%2C%22%24latest_search_keyword%22%3A%22%E6%9C%AA%E5%8F%96%E5%88%B0%E5%80%BC_%E7%9B%B4%E6%8E%A5%E6%89%93%E5%BC%80%22%7D%7D; LGUID=20200424213436-349d3f6f-e4ad-4e09-8574-153f379bb015; _ga=GA1.2.1156153246.1587735278; gate_login_token=e26ac4060ac43576db08c55fd5a972f332b107ab48c066b3ac58e63c1447d5c8; showExpriedIndex=1; showExpriedCompanyHome=1; showExpriedMyPublish=1; hasDeliver=0; privacyPolicyPopup=false; index_location_city=%E5%85%A8%E5%9B%BD; JSESSIONID=ABAAAECABFAACEA999CA7E4E9DF25575E2F90DA2E7F3CF7; WEBTJ-ID=20200502110028-171d353fa231f9-0cb7bf4321c574-c373667-1327104-171d353fa24155; _putrc=10D014753E5A9E41123F89F2B170EADC; login=true; unick=%E9%99%88%E5%BF%97; Hm_lvt_4233e74dff0ae5bd0a3d81c6ccf756e6=1587737530,1587812528,1588084945,1588388429; _gid=GA1.2.1869926791.1588388429; X_MIDDLE_TOKEN=df0037348c6b42da82802d9585708303; TG-TRACK-CODE=search_code; _gat=1; PRE_UTM=; PRE_HOST=; PRE_LAND=https%3A%2F%2Fwww.lagou.com%2Fjobs%2Flist%5Fpython%2Fp-city%5F0%3F%26cl%3Dfalse%26fromSearch%3Dtrue%26labelWords%3D%26suginput%3D; LGSID=20200502142314-d73634fd-e7fc-4874-99c3-fbaad7caeca1; PRE_SITE=https%3A%2F%2Fwww.lagou.com; X_HTTP_TOKEN=f84b41b8fc52df0a30600488512dcabb5eeaad731b; Hm_lpvt_4233e74dff0ae5bd0a3d81c6ccf756e6=1588400604; LGRID=20200502142323-0346fbf9-6648-4e52-8845-2e7f568347aa; SEARCH_ID=474f81d24e30428c9bcc794f80fd7800',
    'origin': 'https://www.lagou.com',
    'x-anit-forge-code': '0',
    'x-anit-forge-token': 'None',
    'x-requested-with': 'XMLHttpRequest'
}


def request_list_page():
    url = "https://www.lagou.com/jobs/positionAjax.json?needAddtionalResult=false"

    data = {
        'first': "false",
        'pn': 1,
        'kd': 'python'
    }
    proxy = [
        {'http': '110.243.20.15:9999'},
        {'http': '123.54.46.104:9999'},
        {'http': '118.25.13.185:8118'},
        {'https': '14.20.235.166:9797'},
        {'http': '222.249.238.138:8080'}
    ]
    for x in range(1, 14):
        data['pn'] = x
        response = requests.post(url, headers=HEADERS, data=data,proxies=proxy[1])
        # print(response.json()) # 如果返回的是json数据,这个方法会把这些json数据自动load成字典
        result = response.json()
        positon_list = result['content']['positionResult']['result']
        for position in positon_list:
            position_id = position['positionId']
            position_url = 'https://www.lagou.com/jobs/%s.html' % position_id
            parse_positon_detail(position_url)
            break
        # time.sleep(2)
        break


def parse_positon_detail(url):
    response = requests.get(url, headers=HEADERS)
    text = response.text
    html = etree.HTML(text)
    position_name = html.xpath("//div[@class='job-name']//h1[@class='name']/text()")[0]
    print(position_name)
    job_request_span = html.xpath("//dd[@class='job_request']//span")
    salary = str(job_request_span[0].xpath(".//text()")[0]).strip()
    city = str(job_request_span[1].xpath(".//text()")[0]).strip().replace('/',"")
    work_year = str(job_request_span[2].xpath(".//text()")[0]).strip().replace('/',"")
    education = str(job_request_span[3].xpath(".//text()")[0]).strip().replace('/',"")
    print("salary:", salary, "city:", city, "work_year:", work_year, "education:",
          education)

    describe = "".join(html.xpath("//dd[@class='job_bt']//div[@class='job-detail']//text()")).strip()
    print(describe)

def main():
    request_list_page()


if __name__ == '__main__':
    main()

selenuim,无需代理,无需寻找Ajax加载的数据,因为是自动化控制浏览器请求网页

import time
from selenium import webdriver
from lxml import etree
import pymongo

client = pymongo.MongoClient(host="192.168.146.153",port=27017)
db = client.lagou
collection = db.positions

class LagouSpider(object):
    # 类属性
    driver_path = r"C:\Program Files (x86)\Google\Chrome\Application\chromedriver.exe"

    def __init__(self):
        self.driver = webdriver.Chrome(executable_path=self.driver_path)
        self.url = "https://www.lagou.com/jobs/list_python/p-city_0?&cl=false&fromSearch=true&labelWords=&suginput="
        self.positions = []

    def run(self):
        self.driver.get(self.url)
        block_btn = self.driver.find_element_by_xpath("//div[@class='body-btn']")  # 进入页面取消红包的按钮
        block_btn.click()
        while True:
            source = self.driver.page_source
            self.parse_list_page(source)
            next_btn = self.driver.find_element_by_xpath("//div[@class='pager_container']/span[last()]")
            if "pager_next pager_next_disabled" in next_btn.get_attribute("class"):
                print("爬取到了最后一页")
                break
            else:
                next_btn.click()
            time.sleep(1)

    def parse_list_page(self, source):
        html = etree.HTML(source)
        links = html.xpath("//a[@class='position_link']/@href")
        for link in links:
            self.parse_detail_page(link)
            time.sleep(1)

    def parse_detail_page(self, link):
        # self.driver.get(url=link)
        self.driver.execute_script(f"window.open('{link}')")  # 要在新的窗口打开详情页面
        self.driver.switch_to.window(self.driver.window_handles[1])
        source = self.driver.page_source
        self.parse_positon_detail(source)
        # 关闭当前的这个详情页面
        self.driver.close()
        # 切换回职位列表页
        self.driver.switch_to.window(self.driver.window_handles[0])

    def parse_positon_detail(self, source):
        html = etree.HTML(source)
        position_name = html.xpath("//div[@class='job-name']//h1[@class='name']/text()")[0]
        job_request_span = html.xpath("//dd[@class='job_request']//span")
        salary = str(job_request_span[0].xpath(".//text()")[0]).strip()
        city = str(job_request_span[1].xpath(".//text()")[0]).strip().replace('/', "")
        work_years = str(job_request_span[2].xpath(".//text()")[0]).strip().replace('/', "")
        education = str(job_request_span[3].xpath(".//text()")[0]).strip().replace('/', "")
        describe = "".join(html.xpath("//dd[@class='job_bt']//div[@class='job-detail']//text()")).strip()
        company = html.xpath("//h3[@class='fl']/em[@class='fl-cn']/text()")[0].strip()
        position = {
            'name': position_name,
            'salary': salary,
            'city': city,
            'work_years': work_years,
            'education': education,
            'describe': describe,
            'company':company
        }
        self.positions.append(position)
        print(position)


if __name__ == '__main__':
    spider = LagouSpider()
    try:
        spider.run()
    except:
        collection.insert_many(spider.positions)

动态网页(通过Ajax技术异步更新网页内容)的爬虫方法总结_第1张图片

你可能感兴趣的:(Python爬虫)