Python+Selenium自动化测试详解selenium相关文档及selenium教程(python)
Python+Selenium自动化测试详解selenium相关文档
一、简介
网页三元素:
- html负责内容;
- css负责样式;
- JavaScript负责动作;
从数据的角度考虑,网页上呈现出来的数据的来源:
- html文件
- ajax接口
- javascript加载
如果用requests对一个页面发送请求,只能获得当前加载出来的部分页面,动态加载的数据是获取不到的,比如下拉滚轮得到的数据。selenium最初是一个自动化测试工具, 而爬虫中使用它主要是为了解决requests无法直接执行JavaScript代码的问题。selenium本质是通过驱动浏览器,完全模拟浏览器的操作,比如跳转、输入、点击、下拉等,来拿到网页渲染之后的结果,可支持多种浏览器。Selenium是python的一个第三方库,对外提供的接口可以操作浏览器,然后让浏览器完成自动化的操作。
selenium在爬虫中的应用:
- 模拟登录
- 便捷的获取动态加载的数据
缺点:
- 爬取数据的效率底
- 环境部署繁琐
二、环境安装
- 下载安装selenium:pip install selenium
- 下载浏览器驱动程序:http://chromedriver.storage.googleapis.com/index.html
- 查看驱动和浏览器版本的映射关系: https://www.cnblogs.com/Summer-skr--blog/p/11715259.html
查看谷歌浏览器版本
下载好以后,就有驱动程序了。
三、基本使用
1.浏览器创建
Selenium支持非常多的浏览器,如Chrome、Firefox、Edge等,还有Android、BlackBerry等手机端的浏览器。另外,也支持无界面浏览器PhantomJS。
from selenium import webdriver browser = webdriver.Chrome() browser = webdriver.Firefox() browser = webdriver.Edge() browser = webdriver.PhantomJS() browser = webdriver.Safari() browser.quit() # 关闭浏览器 browser.close() # 关闭当前页面
close 只会关闭当前窗口,而 quit 退出驱动并会关闭所有的窗口。
2.打开网页
browser.get(url) # 打开path路径 page_text = browser.page_source # 获取当前浏览器页面的源码数据
3.元素定位
查找一个元素(单节点)
element = find_element_by_id() element = find_element_by_name() element = find_element_by_class_name() element = find_element_by_tag_name() element = find_element_by_link_text() element = find_element_by_partial_link_text() element = find_element_by_xpath() element = find_element_by_css_selector()
查找多个元素(多节点)
element = find_elements_by_id() element = find_elements_by_name() element = find_elements_by_class_name() element = find_elements_by_tag_name() element = find_elements_by_link_text() element = find_elements_by_partial_link_text() element = find_elements_by_xpath() element = find_elements_by_css_selector()
注意:
(1)find_element_by_xxx第一个符合条件的标签,find_elements_by_xxx找的是所有符合条件的标签。
(2)根据ID、CSS选择器和XPath获取,它们返回的结果完全一致。
(3)另外,Selenium还提供了通用方法find_element(),它需要传入两个参数:查找方式By和值。实际上,它就是find_element_by_id()这种方法的通用函数版本,比如find_element_by_id(id)就等价于find_element(By.ID, id),二者得到的结果完全一致。
# 通过id定位 <html> <body> <form id="loginForm"> <input name="username" type="text" /> <input name="password" type="password" /> <input name="continue" type="submit" value="Login" /> </form> </body> <html> login_form = driver.find_element_by_id('loginForm')
# 通过name定位 <html> <body> <form id="loginForm"> <input name="username" type="text" /> <input name="password" type="password" /> <input name="continue" type="submit" value="Login" /> <input name="continue" type="button" value="Clear" /> </form> </body> <html> username = driver.find_element_by_name('username') password = driver.find_element_by_name('password')
# 通过链接文本定位 <html> <body> <p>Are you sure you want to do this?</p> <a href="continue.html">Continue</a> <a href="cancel.html">Cancel</a> </body> <html> continue_link = driver.find_element_by_link_text('Continue') continue_link = driver.find_element_by_partial_link_text('Conti')
# 通过标签名定位 <html> <body> <h1>Welcome</h1> <p>Site content goes here.</p> </body> <html> heading1 = driver.find_element_by_tag_name('h1')
# 通过类名定位 <html> <body> <p class="content">Site content goes here.</p> </body> <html> content = driver.find_element_by_class_name('content')
# 通过CSS选择器定位 <html> <body> <p class="content">Site content goes here.</p> </body> <html> content = driver.find_element_by_css_selector('p.content') # 推荐使用xpath定位 username = driver.find_element_by_xpath("//form[input/@name='username']") username = driver.find_element_by_xpath("//form[@id='loginForm']/input[1]") username = driver.find_element_by_xpath("//input[@name='username']")
4.节点操作
ele.text 拿到节点的内容 (包括后代节点的所有内容)
driver.find_element_by_id('gin').text
ele.send_keys("")搜索框输入文字
driver.find_element_by_id('kw').send_keys("Python")
ele.click()标签
driver.find_element_by_id('su').click()
ele.get_attribute("")获取属性值
# 获取元素标签的内容 att01 = a.get_attribute('textContent') # # 获取元素内的全部HTML att02 = a.get_attribute('innerHTML') # # 获取包含选中元素的HTML att03 = a.get_attribute('outerHTML') # 获取该元素的标签类型 tag01 = a_href.tag_name
5.动作链
from selenium.webdriver import ActionChains source = browser.find_element_by_css_selector('') target = browser.find_element_by_css_selector('') actions = ActionChains(browser) actions.drag_and_drop(source, target).perform() actions.release()
6.在页面间切换
适用与页面中点开链接出现新的页面的网站,但是浏览器对象browser还是之前页面的对象
window_handles = driver.window_handles driver.switch_to.window(window_handles[-1])
7.保存网页截图
driver.save_screenshot('screen.png')
8.执行JavaScript
browser.execute_script('window.scrollTo(0, document.body.scrollHeight)')
9.前进和后退
browser.back() browser.forward()
10.等待
from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC driver = webdriver.Chrome() driver.get("http://somedomain/") try: element = WebDriverWait(driver, 10).until( EC.presence_of_element_located((By.ID, "myDynamicElement")) ) finally: driver.quit()
条件
11.Cookie处理
获取、添加、删除Cookies
browser.get_cookies() browser.add_cookie({'name': 'name', 'domain': 'www.zhihu.com', 'value': 'germey'}) browser.delete_all_cookies()
12. 搜索属性值
- 获得element之后搜索
url = driver.find_element_by_name('t2').get_attribute('href')
- 页面源码中搜索
源码中搜索字符串,可以是文本值也可以是属性值 res = driver.page_source.find('字符串') 返回值 -1 未找到 其他 找到
13.谷歌无头浏览器
from selenium.webdriver.chrome.options import Options。 chrome_options = Options() chrome_options.add_argument('--headless') chrome_options.add_argument('--disable-gpu') browser = webdriver.Chrome(executable_path=path, chrome_options=chrome_options)
14.规避监测
相关的网站会对selenium发起的请求进行监测,网站后台可以根据window.navigator.webdriver返回值进行selenium的监测,若返回值为undefinded,则不是selenium进行的请求发送;若为true,则是selenium发起的请求。
规避监测的方法:
from selenium.webdriver import ChromeOptions option = ChromeOptions() option.add_experimental_option('excludeSwitches', ['enable-automation']) bro = webdriver.Chrome(executable_path='chromedriver.exe',options=option)
15. 切换子框架
此操作主要作用与 ifram子框架 的互相切换使用
iframe = driver.find_element_by_xxx('') driver.switch_to_frame(节点对象)
16. 不请求图片模式
只需要如下设置则不会请求图片, 会加快效率
chrome_opt = webdriver.ChromeOptions() prefs = {"profile.managed_default_content_settings.images": 2} chrome_opt.add_experimental_option("prefs", prefs)
四、鼠标键盘操作(ActionChains)
1. ActionChains基本用法
ActionChains的执行原理:当你调用ActionChains的方法时,不会立即执行,而是会将所有的操作按顺序存放在一个队列里,当你调用perform()方法时,队列中的时间会依次执行
有两种调用方法:
链式写法
menu = driver.find_element_by_css_selector(".nav") hidden_submenu = driver.find_element_by_css_selector(".nav #submenu1") ActionChains(driver).move_to_element(menu).click(hidden_submenu).perform()
分步写法
menu = driver.find_element_by_css_selector(".nav") hidden_submenu = driver.find_element_by_css_selector(".nav #submenu1") actions = ActionChains(driver) actions.move_to_element(menu) actions.click(hidden_submenu) actions.perform()
两种写法本质是一样的,ActionChains都会按照顺序执行所有的操作。
2. ActionChains方法列表
click(on_element=None) ——单击鼠标左键 click_and_hold(on_element=None) ——点击鼠标左键,不松开 context_click(on_element=None) ——点击鼠标右键 double_click(on_element=None) ——双击鼠标左键 send_keys(*keys_to_send) ——发送某个键到当前焦点的元素 send_keys_to_element(element, *keys_to_send) ——发送某个键到指定元素 key_down(value, element=None) ——按下某个键盘上的键 key_up(value, element=None) ——松开某个键 drag_and_drop(source, target) ——拖拽到某个元素然后松开 drag_and_drop_by_offset(source, xoffset, yoffset) ——拖拽到某个坐标然后松开 move_by_offset(xoffset, yoffset) ——鼠标从当前位置移动到某个坐标 move_to_element(to_element) ——鼠标移动到某个元素 move_to_element_with_offset(to_element, xoffset, yoffset) ——移动到距某个元素(左上角坐标)多少距离的位置 perform() ——执行链中的所有动作 release(on_element=None) ——在某个元素位置松开鼠标左键
3. 代码示例
(1)点击操作
# -*- coding: utf-8 -*- from selenium import webdriver from selenium.webdriver.common.action_chains import ActionChains from time import sleep driver = webdriver.Firefox() driver.implicitly_wait(10) driver.maximize_window() driver.get('http://sahitest.com/demo/clicks.htm') click_btn = driver.find_element_by_xpath('//input[@value="click me"]') # 单击按钮 doubleclick_btn = driver.find_element_by_xpath('//input[@value="dbl click me"]') # 双击按钮 rightclick_btn = driver.find_element_by_xpath('//input[@value="right click me"]') # 右键单击按钮 ActionChains(driver).click(click_btn).double_click(doubleclick_btn).context_click(rightclick_btn).perform() # 链式用法 print driver.find_element_by_name('t2').get_attribute('value') sleep(2) driver.quit()
element.get_attribute()获取某个元素属性
(2)鼠标移动
# -*- coding: utf-8 -*- from selenium import webdriver from selenium.webdriver.common.action_chains import ActionChains from time import sleep driver = webdriver.Firefox() driver.implicitly_wait(10) driver.maximize_window() driver.get('http://sahitest.com/demo/mouseover.htm') write = driver.find_element_by_xpath('//input[@value="Write on hover"]') # 鼠标移动到此元素,在下面的input框中会显示“Mouse moved” blank = driver.find_element_by_xpath('//input[@value="Blank on hover"]') # 鼠标移动到此元素,会清空下面input框中的内容 result = driver.find_element_by_name('t1') action = ActionChains(driver) action.move_to_element(write).perform() # 移动到write,显示“Mouse moved” print result.get_attribute('value') # action.move_to_element(blank).perform() action.move_by_offset(10, 50).perform() # 移动到距离当前位置(10,50)的点,与上句效果相同,移动到blank上,清空 print result.get_attribute('value') action.move_to_element_with_offset(blank, 10, -40).perform() # 移动到距离blank元素(10,-40)的点,可移动到write上 print result.get_attribute('value') sleep(2)
(3)拖拽
# -*- coding: utf-8 -*- from selenium import webdriver from selenium.webdriver.common.action_chains import ActionChains from time import sleep driver = webdriver.Firefox() driver.implicitly_wait(10) driver.maximize_window() driver.get('http://sahitest.com/demo/dragDropMooTools.htm') dragger = driver.find_element_by_id('dragger') # 被拖拽元素 item1 = driver.find_element_by_xpath('//div[text()="Item 1"]') # 目标元素1 item2 = driver.find_element_by_xpath('//div[text()="Item 2"]') # 目标2 item3 = driver.find_element_by_xpath('//div[text()="Item 3"]') # 目标3 item4 = driver.find_element_by_xpath('//div[text()="Item 4"]') # 目标4 action = ActionChains(driver) action.drag_and_drop(dragger, item1).perform() # 1.移动dragger到item1 sleep(2) action.click_and_hold(dragger).release(item2).perform() # 2.效果与上句相同,也能起到移动效果 sleep(2) action.click_and_hold(dragger).move_to_element(item3).release().perform() # 3.效果与上两句相同,也能起到移动的效果 sleep(2) # action.drag_and_drop_by_offset(dragger, 400, 150).perform() # 4.移动到指定坐标 action.click_and_hold(dragger).move_by_offset(400, 150).release().perform() # 5.与上一句相同,移动到指定坐标 sleep(2) driver.quit()
一般用坐标定位很少,用上例中的方法1足够了,如果看源码,会发现方法2其实就是方法1中的drag_and_drop()的实现。注意:拖拽使用时注意加等待时间,有时会因为速度太快而失败。
(4)按键
模拟按键有多种方法,能用win32api来实现,能用SendKeys来实现,也可以用selenium的WebElement对象的send_keys()方法来实现,这里ActionChains类也提供了几个模拟按键的方法。
# -*- coding: utf-8 -*- from selenium import webdriver from selenium.webdriver.common.action_chains import ActionChains from time import sleep driver = webdriver.Firefox() driver.implicitly_wait(10) driver.maximize_window() driver.get('http://sahitest.com/demo/keypress.htm') key_up_radio = driver.find_element_by_id('r1') # 监测按键升起 key_down_radio = driver.find_element_by_id('r2') # 监测按键按下 key_press_radio = driver.find_element_by_id('r3') # 监测按键按下升起 enter = driver.find_elements_by_xpath('//form[@name="f1"]/input')[1] # 输入框 result = driver.find_elements_by_xpath('//form[@name="f1"]/input')[0] # 监测结果 # 监测key_down key_down_radio.click() ActionChains(driver).key_down(Keys.CONTROL, enter).key_up(Keys.CONTROL).perform() print result.get_attribute('value') # 监测key_up key_up_radio.click() enter.click() ActionChains(driver).key_down(Keys.SHIFT).key_up(Keys.SHIFT).perform() print result.get_attribute('value') # 监测key_press key_press_radio.click() enter.click() ActionChains(driver).send_keys('a').perform() print result.get_attribute('value') driver.quit()
示例2:
# -*- coding: utf-8 -*- from selenium import webdriver from selenium.webdriver.common.action_chains import ActionChains from selenium.webdriver.common.keys import Keys from time import sleep driver = webdriver.Firefox() driver.implicitly_wait(10) driver.maximize_window() driver.get('http://sahitest.com/demo/label.htm') input1 = driver.find_elements_by_tag_name('input')[3] input2 = driver.find_elements_by_tag_name('input')[4] action = ActionChains(driver) input1.click() action.send_keys('Test Keys').perform() action.key_down(Keys.CONTROL).send_keys('a').key_up(Keys.CONTROL).perform() # ctrl+a action.key_down(Keys.CONTROL).send_keys('c').key_up(Keys.CONTROL).perform() # ctrl+c action.key_down(Keys.CONTROL, input2).send_keys('v').key_up(Keys.CONTROL).perform() # ctrl+v print input1.get_attribute('value') print input2.get_attribute('value') driver.quit()
五、使用示例
示例1:打开百度,搜索爬虫
from selenium import webdriver from time import sleep bro = webdriver.Chrome() bro.get(url='https://www.baidu.com/') sleep(2) text_input = bro.find_element_by_id('kw') text_input.send_keys('爬虫') sleep(2) bro.find_element_by_id('su').click() sleep(3) print(bro.page_source) bro.quit()
示例2:获取豆瓣电影中更多电影详情数据(谷歌无头浏览器)
from selenium import webdriver from time import sleep from selenium.webdriver.chrome.options import Options 第1步:下面三行固定 chrome_options = Options() chrome_options.add_argument('--headless') chrome_options.add_argument('--disable-gpu') url = 'https://movie.douban.com/typerank?type_name=%E6%83%8A%E6%82%9A&type=19&interval_id=100:90&action=' 第2步:把chrome_options对象作为参数 bro = webdriver.Chrome(chrome_options=chrome_options) bro.get(url) sleep(3) bro.execute_script('window.scrollTo(0,document.body.scrollHeight)') sleep(3) bro.execute_script('window.scrollTo(0,document.body.scrollHeight)') sleep(3) bro.execute_script('window.scrollTo(0,document.body.scrollHeight)') sleep(2) page_text = bro.page_source with open('./douban.html','w',encoding='utf-8') as fp: fp.write(page_text) print(page_text) sleep(1) bro.quit()
示例3:登录qq空间
在web 中,经常会遇到frame 嵌套页面的应用,使用WebDriver 每次只能在一个页面上识别元素,对于frame 嵌套内的页面上的元素,直接定位是定位是定位不到的。这个时候就需要通过switch_to_frame()方法将当前定位的主体切换了frame 里。先定位到iframe,再在iframe中进行标签定位。否则,定位不到我们想要的标签。
import requests from selenium import webdriver from lxml import etree import time driver = webdriver.Chrome(executable_path=r'C:\Users\Administrator\chromedriver.exe') driver.get('https://qzone.qq.com/') #switch_to操作切换frame,此时才能进行登陆页面的操作。 driver.switch_to.frame('login_frame')
#点击使用账号密码登陆,需要绑定click事件 driver.find_element_by_id('switcher_plogin').click() #driver.find_element_by_id('u').clear() driver.find_element_by_id('u').send_keys('QQ') #driver.find_element_by_id('p').clear() driver.find_element_by_id('p').send_keys('密码') #点击登陆,绑定click事件 driver.find_element_by_id('login_button').click() time.sleep(2) driver.execute_script('window.scrollTo(0,document.body.scrollHeight)') time.sleep(2) driver.execute_script('window.scrollTo(0,document.body.scrollHeight)') time.sleep(2) driver.execute_script('window.scrollTo(0,document.body.scrollHeight)') time.sleep(2) page_text = driver.page_source #获取页面源码数据,注意page_source无括号。 tree = etree.HTML(page_text) #执行解析操作 li_list = tree.xpath('//ul[@id="feed_friend_list"]/li') for li in li_list: text_list = li.xpath('.//div[@class="f-info"]//text()|.//div[@class="f-info qz_info_cut"]//text()') text = ''.join(text_list) print(text+'\n\n\n') driver.quit()
发现小框是嵌套在大框里面的,在当前的html源码中,又嵌套了一个html子页面,这个子页面是包含在iframe标签中的。所以,如果定位的标签是存在于iframe中的,那么一定需要使用switch to函数,将当前浏览器页面的参照物切换到iframe中,iframe中有一个id为login_frame的属性值,可以根据它来定位。
示例4:利用搜狗搜索接口抓取微信公众号(无头、规避检测、等待、切换页面)
# 添加启动参数 (add_argument) # 添加实验性质的设置参数 (add_experimental_option) from selenium import webdriver from selenium.webdriver.support.wait import WebDriverWait import time import requests from lxml import etree option = webdriver.ChromeOptions() option.add_argument('headless') #设置chromedriver启动参数,规避对selenium的检测机制 option.add_experimental_option('excludeSwitches', ['enable-automation']) driver = webdriver.Chrome(chrome_options=option) url = 'http://weixin.sogou.com/weixin?type=1&s_from=input&query=python_shequ' driver.get(url) print(driver.title) timeout = 5 link = WebDriverWait(driver, timeout).until( lambda d: d.find_element_by_link_text('Python爱好者社区')) link.click() time.sleep(1) # 切换页面 window_handles = driver.window_handles driver.switch_to.window(window_handles[-1]) print(driver.title) article_links = WebDriverWait(driver, timeout).until( # EC.presence_of_element_located((By.XPATH, '//h4[@class="weui_media_title"]')) lambda d: d.find_elements_by_xpath('//h4[@class="weui_media_title"]')) article_link_list = [] for item in article_links: article_link = 'https://mp.weixin.qq.com' + item.get_attribute('hrefs') # print(article_link) article_link_list.append(article_link) print(article_link_list) first_article_link = article_link_list[0] header = { 'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/39.0.2171.95 Safari/537.36 OPR/26.0.1656.60 } response = requests.get(first_article_link, headers=header, timeout=5 ) tree = etree.HTML(response.text) title = tree.xpath('//h2[@id="activity-name"]/text()')[0].strip() content = tree.xpath('//div[@id="js_content"]//text()') content = ''.join(content).strip() print(title) print(content)
示例5:用selenium实现一个头条号的模拟发文接口
示例5:用selenium实现一个头条号的模拟发文接口
import time import redis from selenium import webdriver from selenium.webdriver.common.keys import Keys from selenium.webdriver.support.wait import WebDriverWait r = redis.Redis('127.0.0.1', 6379) def toutiao_save_and_preview(title, content, expand_link): option = webdriver.ChromeOptions() option.add_argument('headless') driver = webdriver.Chrome(chrome_options=option) # 获取渲染的正文 driver.get('file:///Users/Documents/toutiao.html') driver.execute_script("contentIn('"+ content +"');") timeout = 5 content_copy = WebDriverWait(driver, timeout).until(lambda d: d.find_element_by_xpath('//button[@class="btn"]')) content_copy.click() # 模拟登录发文页面 cookie_toutiao = [{'name': 'ccid', 'value': 'db43e70fd9404338c49209ba04f7a11f'}, {'name': 'tt_webid', 'value': '6612748996061414925'}, {'name': 'UM_distinctid', 'value': '1667a53d28d449-0e229246a33996-4a506a-1fa400-1667a53d28e361'}, {'name': 'sso_uid_tt', 'value': '4c8179804d74252717c675607c721602'}, {'name': 'toutiao_sso_user', 'value': '8acc9b248cd201034637248021183d5a'}, {'name': 'sso_login_status', 'value': '1'}, {'name': 'sessionid', 'value': '8441fa3fc5ae5bc08c3becc780b5b2df'}, {'name': '_mp_test_key_1', 'value': '6aba81df9e257bea2a99713977f1e33b'}, {'name': 'uid_tt', 'value': '75b5b52039d4c9dd41315d061c833f0b'}, {'name': 'ccid', 'value': '4231c5cd5a98033f2e78336b1809a18a'}, {'name': 'tt_webid', 'value': '6631884089946523149'}, {'name': 'UM_distinctid', 'value': '16783e1566479-0ae7bcdcaeb592-113b6653-13c680-16783e156656d4'}, {'name': 'passport_auth_status', 'value': '99f731f2c6dc150e6dfea46799f20e90'}, {'name': 'sso_uid_tt', 'value': 'f4bcd2cf972384b428449b0479475ce6'}, {'name': 'toutiao_sso_user', 'value': '60df7bb620b4b6d1d17a1de83daec9c1'}, {'name': 'sso_login_status', 'value': '1'}, {'name': 'sessionid', 'value': '786fe64e9186d51b8427290a557b3c7b'}, {'name': 'uid_tt', 'value': '91a7a72a85861ae686fb66177bc16bca'}, {'name': '__tea_sdk__ssid', 'value': '60b289e6-e2a4-4494-a3e8-7936f9731426'}, {'name': 'uuid', 'value': 'w:3ec91cefd76b438583154fea77baa54b'}, {'name': 'tt_im_token', 'value': '1544105894108419437114683515671344747598423336731147829901779697'}] driver.get('https://mp.toutiao.com/profile_v3/index') for cookie in cookie_toutiao: driver.add_cookie(cookie) driver.get('https://mp.toutiao.com/profile_v3/graphic/publish') print(driver.title) # driver.maximize_window() # 写标题 print('写标题') write_title = WebDriverWait(driver, timeout).until(lambda d: d.find_element_by_xpath('//*[@id="title"]')) write_title.click() write_title.send_keys(title) # 粘贴正文 print('写正文') write_content = WebDriverWait(driver, timeout).until(lambda d: d.find_element_by_xpath( '//*[@id="graphic"]/div/div/div[2]/div[1]/div[2]/div[3]/div[2] | //div[contains(@class,"ql-editor")]')) write_content.click() write_content.clear() write_content.send_keys(Keys.SHIFT + Keys.INSERT) # time.sleep(1) # 检测图片上传是否完成 try: if 'img' in content: WebDriverWait(driver, timeout).until( lambda d: d.find_element_by_xpath('//div[@class="pgc-img-wrapper"]')) print('images uploaded success') else: print('no image included') except: print('images uploaded fail') # 页面向下滚动 driver.execute_script("window.scrollTo(0, document.body.scrollHeight)") time.sleep(1) # 添加扩展链接 expand_check = WebDriverWait(driver, timeout).until(lambda d: d.find_element_by_xpath( '//div[@class="pgc-external-link"]//input[@type="checkbox"]', )) expand_check.click() expand_link_box = WebDriverWait(driver, timeout).until(lambda d: d.find_element_by_xpath( '//div[@class="link-input"]//input[@type="text"]', )) expand_link_box.send_keys(expand_link) time.sleep(1) # 自动封面 front_img = WebDriverWait(driver, timeout).until(lambda d: d.find_element_by_xpath( '//div[@class="article-cover"]/div/div[@class="tui2-radio-group"]/label[3]/div/input', )) front_img.click() time.sleep(1) # 保存草稿 save_draft = WebDriverWait(driver, timeout).until(lambda d: d.find_element_by_xpath( '//div[@class="publish-footer"]/button[4]/span')) save_draft.click() time.sleep(1) # 从内容管理页,获取预览链接和文章ID print('get preview_link and article_id') # driver.refresh() preview_link = WebDriverWait(driver, timeout).until(lambda d: d.find_element_by_xpath( '//div[@id="article-list"]//div[@class="master-title"][1]/a')).get_attribute('href') article_id = preview_link.split('=')[-1] print(preview_link, article_id) time.sleep(1) content_management = WebDriverWait(driver, timeout).until(lambda d: d.find_element_by_link_text('内容管理')) content_management.click() time.sleep(1) driver.refresh() preview_link = WebDriverWait(driver, timeout).until(lambda d: d.find_element_by_xpath( '//*[@id="article-list"]/div[2]/div/div/div[1]/div/a')).get_attribute('href') article_id = preview_link.split('=')[-1] index_page = WebDriverWait(driver, timeout).until(lambda d: d.find_element_by_xpath('//a[@class="shead_logo"]')) index_page.click() driver.get('https://mp.toutiao.com/profile_v3/index') print(r.scard('cookie_pool_toutiao')) return preview_link, article_id if __name__ == "__main__": print('start') start_time = time.time() title = 'Children' content = '<p>cute</p><p><img class="wscnph" src="http://img.mp.itc.cn/upload/20170105/1a7095f0c7eb4316954dda4a8b93b88c_th.jpg" /></p>' expand_link = 'https://www.cnblogs.com/Summer-skr--blog/' img = '' preview_link, article_id = toutiao_save_and_preview(title, content, expand_link) print(preview_link) print(article_id) finish_time = time.time() print(finish_time - start_time)
selenium相关文档:
https://www.seleniumhq.org/docs/
https://selenium-python.readthedocs.io
爬虫入门及selenium教程(python)
1.概论
1.1 爬虫是什么
爬虫是在网页上收集信息的程序,一般由两个部分组成,即爬取网页和解析网页。
爬虫的难点有二:1.大规模,高效率爬取 2.对动态网页或反爬机制进行针对。
1.2 爬虫的工具包(python)
爬取库:requests(基础),selenium(操作较复杂,因为模拟浏览器,具有强大的反爬和动态处理能力)
解析库:lxml(xpath),re(正则表达式),bs4(对xpath的封装改造,搜索功能较全面,但是不直接支持xpath)。 re可以对任何文本进行处理,lxml和bs4只能处理html和xml,但可以展示出网页的层级结构。
框架:scrapy,它同时集合了爬取和解析的功能,可以用于开发大型爬虫
本文将重点介绍selenium+lxml的组合
2.最简单的爬虫
用requests进行爬取,re模块进行解析。
爬取T大经管学院官网,看看它一共有多少个系(该校官网有反爬,不适合作为新手教程)
import requests
import re
resource_url = "http://www.sem.tsinghua.edu.cn/"
r = requests.get(resource_url, timeout=20)#爬取网页
data = r.content
data = data.decode('utf-8')
download_list = re.findall(r'href="/\w*/">(\w+)</a>', data)#解析网页,提取所需字段
#re.findall只捕捉分组信息,即括号内字段!
#保存数据&异常处理
if download_list:
with open('result.txt', 'w') as f:
for i in download_list:
f.write('%s\n' % (i))
print('Save as result.txt')
else:
print('No Resource')
3.增加容错:保存原网页
在上面这段程序中,原网页数据以变量的方式储存。假如程序结束或出现异常,这些数据就会丢失,需要重新爬取。而面对反爬机制强大的网站(美团系列)时,多次重复爬取将会导致封号等严重后果。(建议多在百度这种不会封号的网站练习,再去与反爬机制较量)
另外,即使是老手也没办法保证无需debug就能准确解析所需字段,保存原网页可以节省重复爬取的时间。
在上文程序中添加以下代码,即可将原网页保存。
path='./page.txt'
with open(path,'wb') as f:
f.write(r.content)
f.close()
打开txt文件并解析
import re
with open(path, "r",encoding='utf-8') as f:
data = f.read()
download_list = re.findall(r'href="/\w*/">(\w+)</a>', data)
print(download_list)
另外,网页也可被保存为html格式,根据爬取库和解析库来选择保存为txt还是html。下文介绍selenium+lxml的组合,使用html保存。
注1:对于初学者来说,如果没有反爬或复杂需求,可以不学习selenium。 使用requests+lxml/re/bs4或单独使用scrapy已能完成基础需求。
注2:如何看懂html是爬虫的难点,在第6点会对此进行介绍。
4.selenium入门
selenium通过创建模拟浏览器的方式进行爬取,不但可以实现登录等动态操作,而且可以规避跳转反爬。
跳转反爬:通过识别跳转行为来判断访问是否正常。requests和selenium的get方法都相当于地址栏跳转,而常规用户会从网站门页不断向里深入,selenium可以很方便地模仿这一行为。
4.1 安装selenium
参见教程:selenium 安装与 chromedriver安装 , https://www.cnblogs.com/shaosks/p/14857640.html
太长不看版:
1.除了安装selenium包之外,还需要根据自己的浏览器安装驱动,目前selenium只支持Chrome和火狐。我安装的是Chromedriver,以下演示也是使用它。
2.假如不想配置环境变量,需要将驱动同时安装到浏览器目录和python目录下
4.2 简单实例:
在百度图片搜索"python",下滑滚动条加载更多图片,并下载前十张图
from selenium import webdriver
import time
import requests
driver = webdriver.Chrome()#声明浏览器对象
try:
driver.get("https://image.baidu.com")#相当于地址栏跳转
box = driver.find_element_by_id('kw')#找到输入框
box.click()
box.send_keys("python")#先点一下,再输入内容
button=driver.find_element_by_xpath("//input[@type='submit']")#找到按钮"百度一下"
button.click - Domain Name For Sale | Dan.com()#按按钮
time.sleep(2)#在爬虫中多用sleep,而且最好配合random,规避时间反爬(不间隔连续快速操作,有可能被识别为脚本)
for i in range(3):
driver.execute_script("window.scrollTo(0,document.body.scrollHeight)")#用js拖动滚动条
time.sleep(2)
eles=driver.find_elements_by_xpath("//li[@class='imgitem']")#找到图片们,注意这里的find_elements是复数,因此返回一个元素列表
for i,e in enumerate(eles[:10]):
img_data = requests.get(e.get_attribute("data-thumburl")).content#配合requests去到下载地址,用selenium需要反复跳,不方便
img_path = 'spider_demo/'+str(i)+'.jpg'
fp=open(img_path,'wb')
fp.write(img_data)
driver.close()#关闭当前标签页
except Exception as e:
print (e)
程序运行界面如下,selenium会打开一个新的浏览器窗口
4.3 selenium基础语句
4.3.1 搜索元素
find_element_by_id
注意:在html中,为避免与js语法冲突,id唯一,因此优先考虑使用id查找元素
find_element_by_xpath
find_element_by_css_selector
熟练掌握xpath或css_selector的其中一个,在爬取和解析时,它们都能起到作用!
find_element_by_name
find_element_by_tag_name
find_element_by_class_name
4.3.2 元素
网页元素在selenium中被划为WebElement类,有以下常用属性和方法:
element.tag_name #标签名,如 'a'表示<a>元素
element.text #该元素内的文本,例如<span>hello</span>中的'hello'
element.get_attribute(x)# 该元素x属性的值
4.3.3 模拟浏览器操作
鼠标事件,可参见链接:selenium.模拟鼠标操作(ActionChains) - yonugleesin - 博客园
其中重要的鼠标事件摘录如下:
element.click()#点击
element.send_keys()#输入
ActionChains(driver).move_by_offset(xoffset,yoffset).perform()#移动鼠标,有些网页的弹窗需要我们做移开鼠标动作
ActionChains(driver).drag_and_drop_by_offset(source, xoffset, yoffset)#拖拽,多用于自动解验证码。拖滚动条用js语句更方便
其他功能
driver.get()#地址栏跳转
driver.execute_script("window.scrollTo(0,document.body.scrollHeight)")#执行js语句,这句的意思是将滚动条拖至底部
driver.switch_to.window(driver.window_handles[-1])#切换至最新标签页
4.4.4 保存页面源代码
file=open(path,'w',encoding='utf-8')
file.write(repr(driver.page_source))#repr() 函数将对象转化为供解释器读取的形式
file.close()
5.selenium的高级用法
5.1 标签页管理
selenium通过driver.switch_to.window()来切换所操作的标签页。假如一个行动打开了新的标签页,在旧页面无法继续操作新页面元素,必须先switch过去
driver.window_handles属性返回一个列表,列出目前打开的所有标签页,以时间顺序排列,故 driver.switch_to.window(driver.window_handles[-1]) 可以切换至最新标签页
driver.close()方法关闭当前标签页,driver.quit()方法关闭所有打开的标签页,假如所有标签页都被关闭,那么程序就会停止
5.1.1 锚点
一个小技巧:在遍历网页的子页时,可以将目录页保存下来,以便访问完子窗口后回到母窗口
anchor = driver.window_handles[-1]#将当前标签页设为锚点
driver.switch_to.window(anchor)#返回保存的标签页
5.2 反爬!!
5.2.1 常见反爬机制
前文已经介绍过一些,现在做一个总结
检测异常行为:如反复访问,地址栏跳转到网站深处,频繁访问等。可通过构建代理ip池以及优化爬取逻辑等规避
加密网站信息:一种加密方法是,将一些字符串转为图片,这些图片在html中会变为乱码。碰到这些,需要具体问题具体分析
检测selenium:禁止selenium等自动控制软件访问,可通过设置以及js调整
验证码:可通过设置程序自动解验证码或人工守在爬虫旁解决
检测请求头:header中的Cookie、Referer、User-Agent都是检查点,在requests中需要专门设置,selenium一般不会遇到这个问题
5.2.2 selenium反爬
通过调整浏览器选项,提供稳定运行环境并进行反爬。
一般来说,在声明浏览器前后,加上这堆东西,就不会被识别出来,此时还能威胁到我们的只剩异常访问和验证码。验证码的解决方案会在下一点中介绍,但异常访问只能靠优化爬取或者代理池来解决。
options = webdriver.ChromeOptions()
#稳定运行
options.add_argument('-enable-webgl')#解决 GL is disabled的问题
options.add_argument('--no-sandbox') # 解决DevToolsActivePort文件不存在的报错
options.add_argument('--disable-dev-shm-usage')
options.add_argument('--ignore-gpu-blacklist')
options.add_argument('--allow-file-access-from-files')
#反爬
options.add_experimental_option("excludeSwitches", ["enable-automation"])# 模拟真正浏览器
options.add_experimental_option('useAutomationExtension', False)
driver = webdriver.Chrome(options=options)#声明浏览器
#模拟真正浏览器
driver.execute_cdp_cmd("Page.addScriptToEvaluateOnNewDocument", {
"source": """
Object.defineProperties(navigator,{webdriver:{get:() => false}});
"""
})
5.3 动态操作
5.3.1 滚动条到底有多长?
大家肯定见过这样的网站:它不会一次加载完所有信息,需要我们拖滚动条才能继续加载。然而,这个滚动条,到底拖多久才是个头?
我们以百度图片搜索"传承换心"为例,尝试将它的滚动条拖到底端,加载所有图片
from selenium import webdriver
import time
driver = webdriver.Chrome()#声明浏览器对象
try:
driver.get("https://image.baidu.com")#相当于地址栏跳转
box = driver.find_element_by_id('kw')#找到输入框
box.click()
box.send_keys("传承换心")#先点一下,再输入内容
button=driver.find_element_by_xpath("//input[@type='submit']")#找到按钮"百度一下"
button.click - Domain Name For Sale | Dan.com()#按按钮
js='window.scrollTo(0,document.body.scrollHeight)' #下滑到底部
js2='return document.documentElement.scrollHeight' #检测当前滑动条位置
#对于内嵌滚动条(不是网页边缘,而是网页里面可以拖动的东西),需要用getElements把这个元素找出来,再和刚才做一样的操作
#js='document.getElementsByClassName("unifycontainer-two-wrapper")[0].scrollTop=1000000'
#js2='return document.getElementsByClassName("unifycontainer-two-wrapper")[0].scrollHeight'
height=-1
now=driver.execute_script(js2)
#当滑动条位置不再被“下滑到底部”这一行为影响,说明滑动条真的到了底部
while height!=now:
height=now
driver.execute_script(js)
time.sleep(1)
now=driver.execute_script(js2)
time.sleep(5)#对于想要看结果的程序,在最后设置一下暂停
driver.close()
#当然,也可以直接删掉close语句,让浏览器一直开着……
except Exception as e:
print (e)
5.3.2 验证码
验证码,据我所知有三个对策
1.使用cookie避免输入验证码
参见链接:Selenium中遇到验证码问题的处理_嘉 诚的博客-CSDN博客_selenium验证码处理
2.自动识别验证码
验证码种类多样,难以一一列举,和网站加密一样,需要具体问题具体分析
3.最为暴力的人工法
前两种方法泛用性不强,自动识别需要自己设定程序,而有些网站无视cookie,每次登陆都需要验证码。
我使用的方法是在需要输入验证码的环节设置一个time.sleep()。在time.sleep期间,我们可以对selenium打开的浏览器进行操作,比如人工输入验证码,跳转几个页面等。只要在sleep结束后,你去到的网页能够接上后续程序,就不会有任何问题。
许多大型爬虫机构依然使用人工法,因为人工法对劳动力质量和数量要求都不高。
5.4 黑科技:自动录制脚本
对于新手来说,怎么将浏览器操作转化为代码,或许还有难度。我们可以利用selenium IDE浏览器插件,录制在浏览器上的操作,并自动生成代码。
在火狐 更多工具->面向开发者的拓展 一栏可以很方便下载,但是Chrome的插件似乎需要FQ,难顶。
参考链接:自动化测试学习之路(五)-selenium IDE使用
5.5 进阶教程
这篇文章对selenium的介绍更全面详细,适合想要精益求精的大佬
Python爬虫之selenium(全套操作)常用的定位元素与常用方法_瑶山的博客-CSDN博客
6.html入门+lxml与xpath的使用
6.1 查看网页源代码
在浏览器中,随便打开一个网页,点击右键,会看见"检查"选项。点击这一项,即可查看网页源代码。假如把鼠标移动到网页的某个元素上,再选择检查,可以方便地查看到该元素在html中的位置。下图查看的是"百度一下"按钮。
6.2 很简略的html语法
在html中,尖括号<>叫做标签。标签可以成对出现,如head(标题,样式等),body(主体内容)等,也可以单个出现,如input(输入控件),img(图片)等。标签之间可以嵌套,一对标签之中可以夹着任意对标签,这构成了html的层级关系。如下图中,input就是span的子节点。
标签具有属性,以"A"=B的格式书写,说明标签有一个叫A的属性,它的值是B。如下图中,input标签的value属性值为"百度一下"。
注:html中,id属性取值唯一,也就是说id能唯一确定一个标签(假如该标签有这个属性)
关于html的详细教程,可以参考https://www.w3school.com.cn/html/index.asp
6.3 xpath
XPath 是一门在 XML 文档中查找信息的语言,详细教程可参考https://www.cnblogs.com/zhangxinqi/p/9210211.html
这里用例子说明一些常用语法
//img[@title]/@title
查找所有有title属性的img节点,并获取它们的title值。 //表示对当前节点以下所有节点进行搜索,/表示搜索当前节点的子节点,或用于获取属性
//img[@title='PKU']
查找所有title属性为"PKU"的img节点
//@title
查找xml中所有title属性的值。而 /@title 表示查看根节点的title属性,大部分情况下会返回空集,因为根节点无此属性
//img/img2
查找所有img节点下的img2子节点
//img/img2[1]
查找所有img节点下的第一个img2子节点,注意xpath的index不是从0而是从1开始
(//img/img2)[1]
查找所有img节点下的img2子节点,并取第一个
//img/node()
node(),模糊匹配,表示任意子节点。这句意思为查找所有img节点的所有子节点
//a/text()
查找所有a节点的内容
6.4 lxml
lxml是利用xpath查找元素的解析库。节点在lxml中被定义为Element类(和selenium的webElement相似),对于一些难以用xpath表达的复杂需求,可以先用xpath锁定该节点,再调用element类的属性和方法进行处理
关于element类的方法属性,参见官方文档:lxml.etree._Element
以下是一个简单的例子,以下为需要处理的html,节选自大众点评目录页,我们尝试把"4.54"这个数字拿出来
<div class="nebula_star">
<div class="star_icon">
<span class="star star_45 star_sml"></span>
<span class="star star_45 star_sml"></span>
<span class="star star_45 star_sml"></span>
<span class="star star_45 star_sml"></span>
<span class="star star_45 star_sml"></span>
</div>
<div class="star_score score_45 star_score_sml">4.54</div>
</div>
path='demo.html'
html_text = open(path, 'r', encoding='utf-8').read()
r = html.etree.HTML(html_text)#读取html并转为etree
#方法一,直接使用xpath
scorelis=r.xpath("//div[@class='nebula_star']/div[2]/text()")
#但是在实践中,我发现有的<div class="nebula_star">中,并不是都有评分,假如按照方法一,出现缺漏,不知道是哪个漏了,因此最后选用的是以下方法
#方法二
scorelis=[]
scorenode=r.xpath("//div[@class='nebula_star']")
for s in scorenode:
lis=s.findall("div")
if len(lis)==1:
scorelis.append('nan')
else:
scorelis.append(lis[1].text)
#这样,缺少的分数会被转化为nan
7.总结
爬虫是入门容易精通难的技术,在数据分析领域常常用到(当然,一般都是萌新负责的dirty work)。我才疏学浅,权当抛砖引玉,如有错误,还请多多指正。
8.附:美团系列字体加密破解方法
它有专门的字体包,用图片替换了部分文字,下载对应字体包即可。
selenium.模拟鼠标操作(ActionChains)
鼠标事件#
webdriver模块中的件方法:#
clear() #清楚输入框的内容
send_keys('内容') #在文本框内输入内容
click() #点击按钮
submit() #表单的提交
ActionChains模块中的方法:#
click(on_element=None) #单击鼠标左键
click_and_hold(on_element=None) #点击鼠标左键,按住不放
context_click(on_element=None) #点击鼠标右键
double_click(on_element=None) #双击鼠标左键
drag_and_drop(source, target) #拖拽到某个元素然后松开
drag_and_drop_by_offset(source, xoffset, yoffset) #拖拽到某个坐标然后松开
move_by_offset(xoffset, yoffset) #鼠标移动到距离当前位置(x,y)
move_to_element(to_element) #鼠标移动到某个元素
move_to_element_with_offset(to_element, xoffset, yoffset) #将鼠标移动到距某个元素多少距离的位置
release(on_element=None) #在某个元素位置松开鼠标左键
perform() #执行链中的所有动作
ActionChains的两种写法:#
#首先导入模块
from selenium.webdriver.common.action_chains import ActionChains
#链条式方法
searchElement = driver.find_element_by_id('sb_form_q').send_keys('selenium')
searchButtonElement = driver.find_element_by_id('sb_form_go')
ActionChains(driver).click(searchButtonElement).perform()
#分布式方法
searchElement = driver.find_element_by_id('sb_form_q').send_keys('selenium')
searchButtonElement = driver.find_element_by_id('sb_form_go')
ActionChainsDriver = ActionChains(driver).click(searchButtonElement)
ActionChainsDriver.perform()
在12306主页做一个练习,效果如gif#
from selenium.webdriver.common.action_chains import ActionChains
from selenium import webdriver
from time import sleep
get_12306 = webdriver.Firefox()
get_12306.get('https://www.12306.cn/index/index.html')
g_href = get_12306.find_element_by_xpath('//*[@id="J-index"]/a')
Action = ActionChains(get_12306)
for x in range(9):
x = x * 145
print(x)
Action.move_to_element_with_offset(g_href, x, 0).perform()
sleep(0.5)
sleep(2)
get_12306.quit()