爬虫学习(6)

JavaSript动态渲染页面的方式不止Ajax一种。
例如中国青年网,分页部分由JS生成,这其中并不包含Ajax请求;淘宝的页面虽然是Ajax抓取的数据,但是Ajax接口含有很多加密参数,很难找到规律,所以也很难直接分析Ajax来抓取。
对于以上情况,可以模拟浏览器运行,就可以做到在浏览器中看到是什么样,抓取的源码就是什么样,“可见即可爬”。这样,我们就不用在意JS用了什么算法渲染页面,网页后台的Ajax接口有哪些参数。
python中模拟浏览器运行的库有很多,比如Selenium、Splash、PyV8、Ghost等,这里重点介绍Selenium和Splash。

Selenium

Selenium是一个自动化测试工具,可以驱动浏览器执行特定动作,同时可以获取浏览器当前呈现页面的源代码。
首先要确保已经安装好了Chrome以及对应的ChromeDriver,这里我的Chrome版本号是V70,选用的ChromeDriver是V2.43

常见用法

1.声明浏览器对象

1
2
3
4
from selenium import webdriver
browser = webdriver.Chrome()
browser = webdriver.Firefox()
browser = webdriver.Edge()

声明完浏览器对象后,只要让该对象执行各个动作模拟浏览器操作
2.访问页面

1
2
3
4
5
from selenium import webdriver
browser = webdriver.Chrome()
browser.get('https://www.baidu.com')
print(browser.page_source)
browser.close()

用get()方法请求网页,传入URL参数,打印出源代码,关闭浏览器
3.查找节点
Selenium可以驱动浏览器完成各种操作,比如填写表单、模拟点击等,但前提是得知道该节点的位置

  • 单个节点

例如,我们要从淘宝页面中提取搜索框这个节点,首先观察源代码:

可以看到id=‘q’,name=’q’

1
2
3
4
5
6
7
8
from selenium import webdriver
browser = webdriver.Chrome()
browser.get('https://www.taobao.com')
input_first = browser.find_element_by_id('q')
input_second = browser.find_element_by_css_selector('#q')
input_third = browser.find_element_by_xpath('//*[@id="q"]')
print(input_first,input_second,input_third)
browser.close()

下面是获取单个节点的所有方法:

1
2
3
4
5
6
7
8
find_element_by_id
find_element_by_name
find_element_by_xpath
find_element_by_link_text
find_element_by_partial_link_text
find_element_by_tag_name
find_element_by_class_name
find_element_by_css_selector

还有一个通用方法 find_element():
要传入两个参数,第一个参数是查找方式,第二个参数就是值

1
2
3
4
5
6
7
from selenium import webdriver
from selenium.webdriver.common.by import By
browser = webdriver.Chrome()
browser.get('https://www.taobao.com')
input_1 = browser.find_element(By.ID,'q')
print(input_1)
browser.close()

  • 多个节点

find_element只能找到第一个节点,如果要查找所有满足的节点,就要用find_elements()
例如,要查找某宝的左侧导航栏的所有条目:

1
2
3
4
5
6
from selenium import webdriver
browser = webdriver.Chrome()
browser.get('https://www.taobao.com')
lis = browser.find_elements_by_css_selector('.service-bd li')
print(lis)
browser.close()

结果以列表形式展示,每个节点都是WebElement类型
所有获取多个节点的方式与单个节点几乎一致,只是变成了elements
4.节点交互
模拟浏览器执行一些动作,输入文字用send_keys(),清空文字用clear(),点击按钮用click()

1
2
3
4
5
6
7
8
9
10
11
12
from selenium import webdriver
import time
browser = webdriver.Chrome()
browser.get('https://www.taobao.com')
my_input = browser.find_element_by_id('q')
my_input.send_keys('iPhone')
time.sleep(1)
my_input.clear()
my_input.send_keys('iPad')
button = browser.find_element_by_class_name('btn-search')
button.click()
browser.close()

这里首先模拟输入‘iPhone’,过1秒后,清空输入框,输入‘iPad’,然后模拟点击搜索按钮
5.动作链
还有一些操作,是没有特定执行对象的,比如鼠标的拖曳、键盘按键等,如果要模拟这些动作,则要使用动作链来执行
例如下图,试图将左边的小方块拖拽到右边的大方块中:

1
2
3
4
5
6
7
8
9
10
from selenium import webdriver
from selenium.webdriver import ActionChains
browser = webdriver.Chrome()
browser.get('http://www.runoob.com/try/try.php?filename=jqueryui-api-droppable')
browser.switch_to.frame('iframeResult')
source = browser.find_element_by_css_selector('#draggable')
target = browser.find_element_by_css_selector('#droppable')
actions = ActionChains(browser)
actions.drag_and_drop(source,target)
actions.perform()

执行结果如下:

6.执行JS
使用execute_script()
下面就是一个模拟下拉进度条的方法:

1
2
3
4
5
6
#移动到页面最底部
from selenium import webdriver
browser = webdriver.Chrome()
browser.get('https://www.zhihu.com/explore')
browser.execute_script('window.scrollTo(0,document.body.scrollHeight)')
browser.execute_script('alert("To Bottom")')


7.获取节点信息
既然Sselenium有选择节点的方法,那肯定也有方法和属性来提取节点信息。

  • 获取属性
1
2
3
4
5
6
7
8
from selenium import webdriver
browser = webdriver.Chrome()
browser.get('https://www.zhihu.com/explore')
logo = browser.find_element_by_id('zh-top-link-logo')
print(logo)
print(logo.get_attribute('class'))
# <selenium.webdriver.remote.webelement.WebElement (session="f38223661fbd660d5a335d2fd6264e34", element="0.5057014207330262-1")>
# zu-top-link-logo
  • 获取文本值
1
2
3
4
5
6
from selenium import webdriver
browser = webdriver.Chrome()
browser.get('https://www.zhihu.com/explore')
text_input = browser.find_element_by_class_name('zu-top-add-question')
print(text_input.text)
#提问
  • 获取id、位置、标签名和大小
1
2
3
4
5
6
7
8
9
10
11
12
from selenium import webdriver
browser = webdriver.Chrome()
browser.get('https://www.zhihu.com/explore')
entity = browser.find_element_by_class_name('zu-top-add-question')
print(entity.id) #获取id属性值
print(entity.location) #获取相对位置
print(entity.tag_name) #获取标签名称
print(entity.size) #获取节点的大小
# 0.12237199574559199-1
# {'x': 842, 'y': 7}
# button
# {'height': 32, 'width': 66}

8.切换Frame
网页中有一种节点叫‘iframe’,即子Frame,相当于页面的子页面。Selenium默认在父Frame操作,如果页面含有iframe,是无法获取到里面的节点的,所以需要切换Frame

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
from selenium import webdriver
from selenium.common.exceptions import NoSuchElementException
browser = webdriver.Chrome()
browser.get('https://www.runoob.com/try/try.php?filename=jqueryui-api-droppable')
browser.switch_to.frame('iframeResult')
try:
logo = browser.find_element_by_class_name('logo')
except NoSuchElementException:
print('No Logo')
browser.switch_to.parent_frame() #切换回父级Frame
logo = browser.find_element_by_class_name('logo')
print(logo)
print(logo.text)
# No Logo
# <selenium.webdriver.remote.webelement.WebElement (session="68ac17f7a88cf12243a84562a2fd036a", element="0.6314598896265449-2")>
# RUNOOB.COM

9.延时等待
Selenium中,get()方法在网页框架加载结束后结束执行,如果这时候获取page_source,得到的不一定是浏览器完全加载后的页面,可能获取不到额外的Ajax请求,所以需要延时,保证节点已经完全加载出来了

  • 隐式等待
1
2
3
4
5
6
from selenium import webdriver
browser = webdriver.Chrome()
browser.implicitly_wait(10)
browser.get('https://www.zhihu.com/explore')
entity = browser.find_element_by_class_name('zu-top-add-question')
print(entity)

如果Selenium没有在DOM中找到节点,就会继续等待,超出设定的事件后,抛出找不到节点的异常,默认为0

  • 显式等待
    隐式等待方法会受到网络状况的影响,一般我们跟更多的选择显式等待方法
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    from selenium import webdriver
    from selenium.webdriver.support.ui import WebDriverWait
    from selenium.webdriver.support import expected_conditions as EC
    from selenium.webdriver.common.by import By
    browser = webdriver.Chrome()
    browser.get('https://www.taobao.com')
    wait = WebDriverWait(browser,10)
    entity = wait.until(EC.presence_of_element_located((By.ID,'q')))
    button = wait.until(EC.element_to_be_clickable((By.CSS_SELECTOR,'.btn-search')))
    print(entity,button)

WebDriverWait对象指定最长等待时间,然后调用until方法传入等待条件。presence_of_element_located这个条件,表示节点出现的意思(ID为q的节点搜索框),如果十秒钟内,ID为q的节点成功加载,就返回该节点;如果没有加载,就抛出异常;element_to_be_clickable表示CSS选择器为.btn-search的按钮是否是可点击的。
下表是部分等待条件及其含义:

等待条件 含义
title_is 标题是某内容
title_contains 标题包含某内容
presence_of_element_located 节点加载出来,传入定位元组,如(By.ID,’q’)
visibility_of_element_Located 节点可见,传入定位元组
visibility_of 可见,传入节点对象
presence_of_all_elements_located 所有的节点加载出来
text_to_be_present_in_element 某个节点文本包含某文字
text_to_be_present_in_element_value 某个节点值包含某文字
frame_to_be_available_and_switch_to_it 加载并切换
imvisibility_of_element_located 节点不可见

10.前进和后退
浏览器都有前进和后退的功能,Selenium用back()和forward()实现

1
2
3
4
5
6
7
8
9
10
from selenium import webdriver
import time
browser = webdriver.Chrome()
browser.get('https://www.baidu.com')
browser.get('https://www.taobao.com')
browser.get('https://www.zhihu.com/explore')
browser.back()
time.sleep(1)
browser.forward()
browser.close()

11.Cookies
实现获取、添加、删除Cookies等操作

1
2
3
4
5
6
7
8
9
10
11
from selenium import webdriver
browser = webdriver.Chrome()
browser.get('https://www.zhihu.com/explore')
# 获取Cookies
print(browser.get_cookies())
# 添加Cookies
browser.add_cookie({'name':'name','domain':'www.zhihu.com','value':'germey'})
print(browser.get_cookies())
# 删除所有的Cookies
browser.delete_all_cookies()
print(browser.get_cookies()) #[]

12.选项卡管理
访问网页的时候会开启选项卡,可以利用Selenium对选项卡进行操作

1
2
3
4
5
6
7
8
9
10
11
from selenium import webdriver
import time
browser = webdriver.Chrome()
browser.get('https://www.zhihu.com/explore')
browser.execute_script('window.open()')
print(browser.window_handles)
browser.switch_to.window(browser.window_handles[1])
browser.get('https://www.baidu.com')
time.sleep(1)
browser.switch_to.window(browser.window_handles[0])
browser.get('https://www.taobao.com')

调用execute_script(),传入window.open()这个JS语句开启一个新的选项卡。用switch_to.window()切换选项卡,调用window_handles属性获取当前开启的所有选项卡。
13.异常处理
用try except捕获异常

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
from selenium import webdriver
from selenium.common.exceptions import TimeoutException,NoSuchElementException
browser = webdriver.Chrome()
try:
browser.get('https://www.baidu.com')
except TimeoutError:
print('Time Out')

try:
browser.find_element_by_id('hello')
except NoSuchElementException:
print('No Element')
finally:
browser.close()
# No Element