python爬虫系列第四次笔记之动态渲染页面爬取

为了解决异步渲染网页,我们直接模拟浏览器运行的方式来实现,这样就可以左到在浏览器中看到什么样,抓取的源码就是什么样,也就是可见即可爬。这样我们就可以不用管网页内部的javascript用了什么算法渲染页面,也就是所谓的js加密,也不用管网页后台的Ajax接口有哪些参数。

1Selenium的使用

在使用selenium之前,需要安装Driver文件,有ChromeDriver(适用chrome),GeckoDriver(适用firefox),PhantomJS。如何下载到csdn上直接搜索就有,需要注意的是除了PhantomJS以外,另外两个需要和你本机安装的浏览器版本相近(不要求完全一样,但是必须是相近的版本),这是必须要注意的。下载完成后,把exe文件复制粘贴到你安装的python文件中bin文件(或者DOC)同级目录下。

1.1selenium的基本用法

from selenium import webdriver
import time
driver = webdriver.Chrome()
start_url = "https://www.baidu.com"
driver.get(start_url)
time.sleep(5)
driver.close()
driver.quit()

如果运行上面的代码之后,会弹出一个chrome浏览器页面,然后过5秒后退出 ,就说明你的运行文件版本以及安装位置是正确的,当然,这里可以换成Firefox().
那么我们还可以做一些什么事呢?

#coding=utf-8
from selenium import webdriver
import time
driver = webdriver.Chrome()
start_url = "https://www.baidu.com"
driver.get(start_url)
driver.find_element_by_id("kw").send_keys("长城")
driver.find_element_by_id("su").click()
time.sleep(5)
driver.quit()

我们这里根据id定位元素,定位一个位置并给予它一个参数,然后下面使用click()方法实行点击一个按钮。实际上这里是给我们的百度输入框中输入长城这个值,然后点击“百度”按钮。最后过5秒钟,关闭页面。
那么继续

from selenium import webdriver
import time
driver = webdriver.Chrome()
start_url = "https://www.baidu.com"
driver.get(start_url)
driver.find_element_by_id("kw").send_keys("长城")
driver.find_element_by_id("su").click()
time.sleep(5)
print(driver.page_source.encode('GBK','ignore').decode('GBK'))
driver.quit()

这里使用了编码解码的操作,为什么要这么做,是因为网页本身是utf-8,但我们爬取网页时用的是Unicode,但使用print()打印时,里面的内容应该是GBK编码,那么就需要我们将需要打印的数据后加上 .encode(‘GBK’,‘ignore’).decode(‘GBk’)第一个GBK是忽略掉非法字符,然后再译码。不然会报编码错误。这里会打印出网页的源码。
小结:

  1. selenium的导包:from selenium import webdriver
  2. selenium创建driver对象:webdriver.PhantomJS()
  3. selenium请求数据:driver.get("http://www.baidu.com/")
  4. selenium查看数据: driver.page_source
  5. 关闭无界面浏览器: driver.quit()
  6. 根据id定位元素: driver.find_element_by_id(“kw”)
  7. 操作点击事件: click()
  8. 给输入框赋值:send_keys()

1.2selenium元素定位的方法

1.2.1定位方法

find_element_by_id (返回一个元素)
find_elements_by_xpath (返回一个包含元素的列表)
find_elements_by_link_text (根据连接文本获取元素列表)
find_elements_by_partial_link_text (根据链接包含的文本获取元素列表)
find_elements_by_tag_name (根据标签名获取元素列表)
find_elements_by_class_name (根据类名获取元素列表)

注意: find_elementfind_elements的区别 by_link_textby_partial_link_tex的区别:全部文本和包含某个文本
这些解析方法在之前都有介绍,这里就不一一赘述,我们用一个例子来介绍这些方法。

from selenium import webdriver

  driver = webdriver.Chrome()

  driver.get("https://www.douban.com/")

  ret1 = driver.find_element_by_id("anony-nav")
  print(ret1)
  # 输出为:<selenium.webdriver.remote.webelement.WebElement (session="ea6f94544ac3a56585b2638d352e97f3", element="0.5335773935305805-1")>

  ret2 = driver.find_elements_by_id("anony-nav")
  print(ret2)
  #输出为:[<selenium.webdriver.remote.webelement.WebElement (session="ea6f94544ac3a56585b2638d352e97f3", element="0.5335773935305805-1")>]

  ret3 = driver.find_elements_by_xpath("//*[@id='anony-nav']/h1/a")
  print(len(ret3))
   #输出为:1

  ret4 = driver.find_elements_by_tag_name("h1")
  print(len(ret4))
   #输出为:1

  ret5 = driver.find_elements_by_link_text("下载豆瓣 App")
  print(len(ret5))
   #输出为:1

  ret6 = driver.find_elements_by_partial_link_text("豆瓣")
  print(len(ret6))
   #输出为:20

  driver.close()

这里调用的方法并不复杂,只是需要去网页中找到你想要的元素的id,或者class,或者xpath方法,需要注意的是xpath路径是可以直接在网页中右键点击elements中的元素,copy–》xpath,来获得。
还有css元素定位

from selenium import webdriver

driver = webdriver.Chrome()
driver.get('https://www.baidu.com')
driver.find_element_by_class_name('s_ipt').send_keys('3浪还有')
# driver.find_element_by_css_selector('#su').click()
driver.find_element_by_class_name('btn self-btn bg s_btn').click()
#其他css方法
#  通过id属性(css属性)
driver.find_element_by_css_selector("#kw").send_keys("python")
#  通过class属性定位(css属性)
driver.find_element_by_css_selector(".s_ipt").send_keys("python")
#  通过标签定位(css属性)
driver.find_element_by_css_selector("input").send_keys("python")
#  通过name属性定位(其他属性)
driver.find_element_by_css_selector("[name='wd']").send_keys("python")
#  通过autocomplete属性定位(其他属性)
driver.find_element_by_css_selector("[autocomplete='off']").send_keys("python")
#  通过type属性定位(其他属性)
driver.find_element_by_css_selector("[type='text']").send_keys("python")
#  css也可以通过标签与属性的组合来定位元素
driver.find_element_by_css_selector("input.s_ipt").send_keys("python")
driver.find_element_by_css_selector("input#kw").send_keys("python")
driver.find_element_by_css_selector("input[id='kw']").send_keys("python")
#  css层级关系
driver.find_element_by_css_selector("form#form>span>input").send_keys("python")
driver.find_element_by_css_selector("form.fm>span>inout").send_keys("python")
#  css逻辑运算
driver.find_element_by_css_selector("input[id='kw'][name='wd']").send_keys("python")

那么我们获得,或者说定位到了元素后我们需要怎么办呢?

1.2.2解析手段

在第一章介绍了赋予参数方法,和点击方法。那么还有什么方法?
find_element仅仅能够获取元素,不能够直接获取其中的数据,如果需要获取数据需要使用以下方法:

  • 获取文本:element.text
  • 获取属性值:element.get_attribute("href")
    示例:
#coding=gbk
from selenium import webdriver

driver =webdriver.Chrome()

driver.get("https://www.douban.com/")

ret4 = driver.find_elements_by_tag_name("h1")
print(ret4[0].text)
#输出:豆瓣

ret5 = driver.find_elements_by_link_text("下载豆瓣 App")
print(ret5[0].get_attribute("href"))
#输出:https://www.douban.com/doubanapp/app?channel=nimingye

driver.close()

这里给出了返回txt内容和href,是不是和上一章的解析方法有些类似。
小结

  1. 根据xpath定位元素:driver.find_elements_by_xpath("//*[@id='s']/h1/a")
  2. 根据class定位元素:driver.find_elements_by_class_name("box")
  3. 根据link_text定位元素:driver.find_elements_by_link_text("下载豆瓣 App")
  4. 根据tag_name定位元素:driver.find_elements_by_tag_name("h1")
  5. 获取文本内容:element.text
  6. 获取标签属性: element.get_attribute("href")

1.2.3其他方法

selenium 处理cookie

通过driver.get_cookies()能够获取所有的cookie

# 把cookie转化为字典
{cookie[‘name’]: cookie[‘value’] for cookie in driver.get_cookies()}

#删除一条cookie
driver.delete_cookie("CookieName")
# 删除所有的cookie
driver.delete_all_cookies()

页面等待

  • 为什么需要等待

    如果网站采用了动态html技术,那么页面上的部分元素出现时间便不能确定,这个时候就可以设置一个等待时间,强制要求在时间内出现,否则报错

  • 页面等待的方法 time.sleep(10)

switch方法切换的操作

一个浏览器肯定会有很多窗口,所以我们肯定要有方法来实现窗口的切换。切换窗口的方法如下:

也可以使用 window_handles 方法来获取每个窗口的操作对象。例如:


    # 1. 获取当前所有的窗口
    current_windows = driver.window_handles

    # 2. 根据窗口索引进行切换
    driver.switch_to.window(current_windows[1])

iframe是html中常用的一种技术,即一个页面中嵌套了另一个网页,selenium默认是访问不了frame中的内容的,对应的解决思路是

driver.switch_to.frame()

在使用selenium登录qq邮箱的过程中,我们会发现,无法在邮箱的登录input标签中输入内容,通过观察源码可以发现,form表单在一个frame中,所以需要切换到frame中。这个方法使用的很多。

当你触发了某个事件之后,页面出现了弹窗提示,处理这个提示或者获取提示信息方法如下:

    alert = driver.switch_to_alert()

页面前进和后退

driver.forward()     #前进
driver.back()        # 后退

selenium的优缺点

  • selenium能够执行页面上的js,对于js渲染的数据和模拟登陆处理起来非常容易
  • selenium由于在获取页面的过程中会发送很多请求,所以效率非常低,所以在很多时候需要酌情使用

小结

  1. 获取cookie: get_cookies()
  2. 删除cookie: delete_all_cookies()
  3. 切换窗口:switch_to.window()
  4. 切换iframe: switch_to.frame()

1.4实例说明

在给出例子之前,我们要想到,动态解析页面的时候,我们需要获取在网页上所有的内容,就必须要渲染出网页全部的页面,不然我们可能就会定位不到我们想要的信息,所以我们就需要进行页面的滚动,一直到页面最底部。
方法为:

from selenium import webdriver
import time, random

# 1.创建driver对象
driver = webdriver.Chrome()
# 访问url地址
driver.get('https://www.csdn.net')
for i in range(1, 10):
    # 构造滑动函数,scrollTo滚动,参数为滑动的距离
    js = 'scrollTo(0, {})'.format(1000 * i)
    # execute_script 执行滑动轨迹
    driver.execute_script(js)
    time.sleep(random.randint(1, 3))
print(driver.page_source)

这样运行后,页面就会滑到最底部,无需记忆,直接复制粘贴就行。
我们来一个比较狠的,我们来做一个斗鱼的弹幕机。

from selenium import webdriver
from lxml import etree
import time
import re

class DySpider(object):
    def __init__(self):
        self.driver = webdriver.Chrome()
        # 最大化窗口
        self.driver.maximize_window()
        self.start_url = 'https://www.douyu.com/directory/all'
        self.driver.get('https://passport.douyu.com/member/login?')
        time.sleep(10)

    def parse_url(self):
        """
        关闭弹窗,执行滑动
        :return:html_element(elements内容), num(最大页码数)
        """
        self.driver.get(self.start_url)
        try:
            """访问后,没有弹窗,在此使用try嵌套"""
            time.sleep(3)
            self.driver.find_element_by_class_name('ZoomTip-tipHide').click()
        except:
            """没有弹窗,则pass"""
            pass
        """获取elements内容"""
        time.sleep(2)
        self.page_url()
        html_element = self.driver.page_source
        print(html_element)
        """提取翻页的最大页码数"""
        num = re.findall('dy-Pagination-item dy-Pagination-item-(\d+)',html_element)[5]
        """验证是否正常(成功)获取"""
        print('返回的页面共有:' + num)
        return num

    def page_url(self, num=None):
        """
        构造向下滑动
        :return:
        """
        if num is None:
            num = 10000
        """每次向下滑动距离10000"""
        js = 'scrollTo(0, {})'.format(num)
        """执行滑动"""
        self.driver.execute_script(js)
        """滑动之后沉睡等待2秒"""
        time.sleep(2)

    def parse_page_url(self, num):
        """
        执行翻页
        :param num: 页码数
        """
        """根据最大页码数的遍历,来执行点击下一页的次数"""
        for i in range(int(num) - 2):
            """调用滑动函数"""
            self.page_url()
            time.sleep(2)
            """下一页的点击事件"""
            self.driver.find_element_by_css_selector('#listAll > section.layout-Module.js-ListContent > div.layout-Module-container.layout-Cover.ListContent > div > ul > li.dy-Pagination-next > span').click()
            """每次成功下一页之后,获取其elements内容"""
            html_data = self.driver.page_source
            html_element = etree.HTML(html_data)
            self.save_id_data(html_element)

    def save_id_data(self, html_element):
        """
        :param html_element: etree转换之后的对象
        """
        # 获取直播间的id列表
        # html_element = etree.HTML(html_element)
        id_num = html_element.xpath('//*[@id="listAll"]/section[2]/div[2]/ul/li/div/a[1]/@href')
        # 直播间id去重
        # id_num = list(set(id_num))
        print(id_num)
        # 访问第一个直播间,达到去除窗口的效果
        js = 'window.open("{}");'.format('https://www.douyu.com' + id_num[0])
        self.driver.execute_script(js)
        time.sleep(2)
        # 获取所有浏览器窗口
        windows = self.driver.window_handles
        # 窗口切换,切换到新打开的直播间窗口
        self.driver.switch_to.window(windows[1])
        # 关闭打开的窗口
        self.driver.close()
        # 切换到初始窗口
        self.driver.switch_to.window(windows[0])
        for id in id_num:
            # 直播间的url地址拼接
            url = 'https://www.douyu.com' + id
            # 调用函数
            self.requests_url_by(url)
            # print(url)
    def requests_url_by(self, url):
        """
        访问直播
        :param url: 直播间的url地址
        """
        # js代码执行打开url--直播间的url地址
        js = 'window.open("{}");'.format(url)
        self.driver.execute_script(js)
        # 隐式等待
        self.driver.implicitly_wait(10)
        # 获取当前所有窗口,返回一个列表
        windows = self.driver.window_handles
        # 窗口切换,下标操作
        self.driver.switch_to.window(windows[1])
        try:
            for i in range(1, 3):
                text = '1'
                self.driver.find_element_by_css_selector('.ChatSend-txt').send_keys(text)  # 输入弹幕
                self.driver.find_element_by_css_selector('.ChatSend-button ').click()  # 点击发送弹幕
                print(f'弹幕----------已发送----------第{i}次')
                time.sleep(1.5)  # 每一秒发送一次共发送20次
        except:
            print('发送失败')
        self.driver.close()
        self.driver.switch_to.window(windows[0])

    def run(self):
        num = self.parse_url()
        # self.save_id_data(html_element)
        self.parse_page_url(num)


if __name__ == '__main__':
    dy = DySpider()
    dy.run()

这个案例会遍历现在斗鱼开播的直播间,然后在每个直播间发两遍‘1’,但是你需要首先在弹出来斗鱼登陆界面,扫码登陆你的斗鱼账号,不然你是不能发弹幕。该案例中用了很多方法,融汇贯通会很适合。我将在下一期讲解类方法的使用。

  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值