selenium爬虫
Web自动化测试工具,可运行在浏览器,根据指令操作浏览器,只是工具,必须与第三方浏览器结合使用,相比于之前学的爬虫只是慢了一点而已。而且这种方法爬取的东西不用在意时候ajax动态加载等反爬机制。因此找标签可以直接F12找,不用确定源码中是否存在。
安装
Linux: sudo pip3 install selenium
Windows: python -m pip install selenium
phantomjs浏览器
phantomjs浏览器又叫做无界面浏览器(又称无头浏览器),在内存中进行页面加载,运行高效。
安装(phantomjs(无界面浏览器)、chromedriver(谷歌浏览器)、geckodriver(火狐浏览器))
Windows
1、下载对应版本的phantomjs、chromedriver、geckodriver
2、chromedriver下载与谷歌浏览器对应的版本,把chromedriver.exe拷贝到python安装目录的Scripts目录下(添加到系统环境变量),查看python安装路径: where python
3、验证,cmd命令行: chromedriver
Linux
1、下载后解压:tar -zxvf geckodriver.tar.gz
2、拷贝解压后文件到 /usr/bin/ (添加环境变量):sudo cp geckodriver /usr/bin/
3、更改权限
sudo -i
cd /usr/bin/
chmod 777 geckodriver
示例代码一:使用 selenium+谷歌浏览器 打开百度,并截图百度首页
from selenium import webdriver browser = webdriver.Chrome() # 创建浏览器对象 browser.get(\'http://www.baidu.com/\') # 打开百度 browser.save_screenshot(\'baidu.png\') # 截屏 browser.quit() # 退出浏览器
示例代码二:打开百度,搜索赵丽颖
from selenium import webdriver import time # 创建浏览器对象 - 已经打开了浏览器 browser = webdriver.Chrome() browser.get(\'http://www.baidu.com/\') # 打开百度 ele = browser.find_element_by_xpath(\'//*[@id="kw"]\') # 找到搜索框 ele.send_keys(\'赵丽颖\') # 向搜索框发送文字: 赵丽颖 time.sleep(1) # 找到 百度一下 按钮,点击一下 browser.find_element_by_xpath(\'//*[@id="su"]\').click() time.sleep(2) browser.quit() # 关闭浏览器
browser浏览器对象方法
- browser = webdriver.Chrome(executable_path=\’path\’) path为浏览器驱动地址
- browser.get(url) 打开path路径
- browser.page_source: 查看响应内容(网页源代码)
- browser.page_source.find(\’字符串\’):从html源码中搜索指定字符串,没有找到返回:-1
- browser.quit():关闭浏览器
元素查找
单元素查找(1个节点对象)
- browser.find_element_by_id(\’\’)
- browser.find_element_by_name(\’\’)
- browser.find_element_by_class_name(\’\’)
- browser.find_element_by_xpath(\’\’)
- browser.find_element_by_link_text(\’\’)
- … …
多元素查找([节点对象列表])
- browser.find_elements_by_id(\’\’)
- browser.find_elements_by_name(\’\’)
- browser.find_elements_by_class_name(\’\’)
- browser.find_elements_by_xpath(\’\’)
- … …
节点对象操作
- .send_keys(\’\’) 搜索框发送内容
- .click() 点击
- .text 获取文本内容
- .get_attribute(\’src\’) 获取属性值
- .find(“”) 查找响应中的字符串
from selenium import webdriver browser = webdriver.Chrome() browser.get(\'https://www.qiushibaike.com/text/\') # 单元素查找 div = browser.find_element_by_class_name(\'content\') print(div.text) # 多元素查找: [<selenium xxx at xxx>,<selenium xxx >] divs = browser.find_elements_by_class_name(\'content\') for div in divs: print(\'*************************\') print(div.text) print(\'*************************\') browser.quit() # 退出浏览器
京东爬虫案例
目标网址 :https://www.jd.com/
抓取目标 :商品名称、商品价格、评价数量、商品商家
思路提醒
- 打开京东,到商品搜索页
- 匹配所有商品节点对象列表
- 把节点对象的文本内容取出来,查看规律,是否有更好的处理办法?
- 提取完1页后,判断如果不是最后1页,则点击下一页
实现步骤
找节点
- 首页搜索框 : //*[@id=”key”]
- 首页搜索按钮 ://*[@id=”search”]/div/div[2]/button
- 商品页的 商品信息节点对象列表 ://*[@id=”J_goodsList”]/ul/li
执行JS脚本,获取动态加载数据
browser.execute_script(\’window.scrollTo(0,document.body.scrollHeight)\’)
from selenium import webdriver import time class JdSpider(object): def __init__(self): self.i = 0 self.url = \'https://www.jd.com/\' self.browser = webdriver.Chrome() # 获取页面信息 - 到具体商品的页面 def get_html(self): self.browser.get(self.url) self.browser.find_element_by_xpath(\'//*[@id="key"]\').send_keys(\'爬虫书\') # 搜索框输入“爬虫书” self.browser.find_element_by_xpath(\'//*[@id="search"]/div/div[2]/button\').click() # 点击搜索 time.sleep(3) # 给商品页面加载时间 # 解析页面 def parse_html(self): # 把下拉菜单拉到底部,执行JS脚本 self.browser.execute_script(\'window.scrollTo(0,document.body.scrollHeight)\') time.sleep(2) # 提取所有商品节点对象列表 li列表 li_list = self.browser.find_elements_by_xpath(\'//*[@id="J_goodsList"]/ul/li\') for li in li_list: info_list = li.text.split(\'\n\') if info_list[0].startswith(\'每满\') or info_list[1].startswith(\'¥\'): price = info_list[1] name = info_list[2] comment = info_list[3] shop = info_list[4] elif info_list[0].startswith(\'单件\'): price = info_list[3] name = info_list[4] comment = info_list[5] shop = info_list[6] else: price = info_list[0] name = info_list[1] comment = info_list[2] shop = info_list[3] print(price, comment, shop, name) # 主函数 def main(self): self.get_html() while True: self.parse_html() # 判断是否该点击下一页,没有找到说明不是最后一页 if self.browser.page_source.find(\'pn-next disabled\') == -1: self.browser.find_element_by_class_name(\'pn-next\').click() time.sleep(2) else: break print(self.i) if __name__ == \'__main__\': spider = JdSpider() spider.main()
chromedriver设置无界面模式
from selenium import webdriver options = webdriver.ChromeOptions() # 设置无界面 options.add_argument(\'--headless\') # 添加无界面参数 browser = webdriver.Chrome(options=options) browser.get(\'http://www.baidu.com/\') browser.save_screenshot(\'baidu.png\') browser.quit()
把上面的代码改为无界面模式
from selenium import webdriver import time class JdSpider(object): def __init__(self): self.url = \'https://www.jd.com/\' self.options = webdriver.ChromeOptions() # 设置无界面 self.options.add_argument(\'--headless\') # 添加无界面参数 # 正常创建浏览器对象即可 self.browser = webdriver.Chrome(options=self.options) self.i = 0 # 统计商品数 # 获取页面信息 - 到具体商品的页面 def get_html(self): self.browser.get(self.url) self.browser.find_element_by_xpath(\'//*[@id="key"]\').send_keys(\'爬虫书\') # 搜索框输入“爬虫书” self.browser.find_element_by_xpath(\'//*[@id="search"]/div/div[2]/button\').click() # 点击搜索 time.sleep(3) # 给商品页面加载时间 def parse_html(self): # 把进度条拉到底部,使所有数据动态加载 self.browser.execute_script(\'window.scrollTo(0,document.body.scrollHeight)\') time.sleep(2) # 等待动态数据加载完成 # 提取所有商品节点对象列表 li列表 li_list = self.browser.find_elements_by_xpath(\'//*[@id="J_goodsList"]/ul/li\') item = {} for li in li_list: # find_element: 查找单元素 item[\'name\'] = li.find_element_by_xpath(\'.//div[@class="p-name"]/a/em\').text.strip() item[\'price\'] = li.find_element_by_xpath(\'.//div[@class="p-price"]\').text.strip() item[\'comment\'] = li.find_element_by_xpath(\'.//div[@class="p-commit"]/strong\').text.strip() item[\'shop\'] = li.find_element_by_xpath(\'.//div[@class="p-shopnum"]\').text.strip() print(item) self.i += 1 def main(self): self.get_html() while True: self.parse_html() # 判断是否为最后一页 if self.browser.page_source.find(\'pn-next disabled\') == -1: self.browser.find_element_by_class_name(\'pn-next\').click() time.sleep(3) else: break print(\'商品数量:\', self.i) self.browser.quit() if __name__ == \'__main__\': spider = JdSpider() spider.main()
View Code
键盘操作
from selenium.webdriver.common.keys import Keys browser = webdriver.Chrome() browser.get(\'http://www.baidu.com/\') # 1、在搜索框中输入"selenium" browser.find_element_by_id(\'kw\').send_keys(\'赵丽颖\') # 2、输入空格 browser.find_element_by_id(\'kw\').send_keys(Keys.SPACE) # 3、Ctrl+a 模拟全选 browser.find_element_by_id(\'kw\').send_keys(Keys.CONTROL, \'a\') # 4、Ctrl+c 模拟复制 browser.find_element_by_id(\'kw\').send_keys(Keys.CONTROL, \'c\') # 5、Ctrl+v 模拟粘贴 browser.find_element_by_id(\'kw\').send_keys(Keys.CONTROL, \'v\') # 6、输入回车,代替 搜索 按钮 browser.find_element_by_id(\'kw\').send_keys(Keys.ENTER)
鼠标操作
import time from selenium import webdriver # 导入鼠标事件 from selenium.webdriver import ActionChains browser = webdriver.Chrome() browser.get(\'http://www.baidu.com/\') # 找到“设置”节点 element = browser.find_element_by_xpath(\'//*[@id="u1"]/a[8]\') # 把鼠标移动到 设置 节点,move_to_element() actions = ActionChains(browser) actions.move_to_element(element) actions.perform() # perform()是真正执行操作 time.sleep(1) # 找到高级设置节点,并点击 browser.find_element_by_link_text(\'高级搜索\').click()
切换页面
适用与页面中点开链接出现新的页面的网站,但是浏览器对象browser还是之前页面的对象
all_handles = browser.window_handles 获取当前所有句柄(窗口)
browser.switch_to_window(all_handles[1]) 切换browser到新的窗口,获取新窗口对象
民政部网站
将民政区划代码爬取到数据库中,按照层级关系(分表 — 省表、市表、县表)
数据库中建表
# 建库 create database govdb charset utf8; use govdb; # 建表 create table province( p_name varchar(20), p_code varchar(20) )charset=utf8; create table city( c_name varchar(20), c_code varchar(20), c_father_code varchar(20) )charset=utf8; create table county( x_name varchar(20), x_code varchar(20), x_father_code varchar(20) )charset=utf8;
思路
- selenium+Chrome打开一级页面,并提取二级页面最新链接
- 增量爬取: 和数据库version表中进行比对,确定之前是否爬过(是否有更新)
- 如果没有更新,直接提示用户,无须继续爬取
- 如果有更新,则删除之前表中数据,重新爬取并插入数据库表
- 最终完成后: 断开数据库连接,关闭浏览器
from selenium import webdriver import pymysql class GovSpider(object): def __init__(self): # 设置无界面 options = webdriver.ChromeOptions() options.add_argument(\'--headless\') self.browser = webdriver.Chrome(options=options) # 添加参数 self.one_url = \'http://www.mca.gov.cn/article/sj/xzqh/2019/\' # 创建数据库和相关变量 self.db = pymysql.connect(\'localhost\', \'root\', \'123456\', \'govdb\', charset=\'utf8\') self.cursor = self.db.cursor() # 创建3个列表,用来executemany()往3张表中插入记录 self.province_list = [] self.city_list = [] self.county_list = [] # 获取首页,并提取二级页面链接(虚假链接) def get_incr_url(self): self.browser.get(self.one_url) # 提取最新链接,判断是否需要增量爬 td = self.browser.find_element_by_xpath(\'//td[@class="arlisttd"]/a[contains(@title,"代码")]\') # 提取链接 和 数据库中做比对,确定是否需要怎俩那个抓取 # get_attribute()会自动补全提取的链接 two_url = td.get_attribute(\'href\') # result为返回的受影响的条数 result = self.cursor.execute(\'select url from version where url=%s\', [two_url]) if result: print(\'无须爬取\') else: td.click() # 切换句柄 all_handlers = self.browser.window_handles self.browser.switch_to.window(all_handlers[1]) self.get_data() # 数据抓取 # 把URL地址存入version表 self.cursor.execute(\'delete from version\') self.cursor.execute(\'insert into version values(%s)\', [two_url]) self.db.commit() # 二级页面中提取行政区划代码 def get_data(self): # 基准xpath tr_list = self.browser.find_elements_by_xpath(\'//tr[@height="19"]\') for tr in tr_list: code = tr.find_element_by_xpath(\'./td[2]\').text.strip() name = tr.find_element_by_xpath(\'./td[3]\').text.strip() print(name, code) # 判断层级关系,添加到对应的数据库表中(对应表中字段) # province: p_name p_code # city : c_name c_code c_father_code # county : x_name x_code x_father_code # 把数据添加到对应的表中 if code[-4:] == \'0000\': self.province_list.append([name, code]) if name in [\'北京市\', \'天津市\', \'上海市\', \'重庆市\']: self.city_list.append([name, code, code]) elif code[-2:] == \'00\': self.city_list.append([name, code, (code[:2] + \'0000\')]) else: if code[:2] in [\'11\', \'12\', \'31\', \'50\']: self.county_list.append([name, code, (code[:2] + \'0000\')]) else: self.county_list.append([name, code, (code[:4] + \'00\')]) # # 和for循环同缩进,所有数据爬完后统一excutemany(), # 执行数据库插入语句 self.insert_mysql() def insert_mysql(self): # 1. 更新时一定要先删除表记录 self.cursor.execute(\'delete from province\') self.cursor.execute(\'delete from city\') self.cursor.execute(\'delete from county\') # 2. 插入新数据 self.cursor.executemany(\'insert into province values(%s,%s)\', self.province_list) self.cursor.executemany(\'insert into city values(%s,%s,%s)\', self.city_list) self.cursor.executemany(\'insert into county values(%s,%s,%s)\', self.county_list) # 3.提交到数据库执行 self.db.commit() print(\'数据抓取完成,成功存入数据库\') def main(self): self.get_incr_url() self.cursor.close() # 所有数据处理完成后断开连接 self.db.close() self.browser.quit() # 关闭浏览器 if __name__ == \'__main__\': spider = GovSpider() spider.main()
SQL命令练习
1. 查询所有省市县信息(多表查询实现)
select province.p_name,city.c_name,county.x_name from province,city,county where province.p_code=city.c_father_code and city.c_code=county.x_father_code;
2. 查询所有省市县信息(连接查询实现)
select province.p_name,city.c_name,county.x_name from province inner join city on province.p_code=city.c_father_code inner join county on city.c_code=county.x_father_code;
Web客户端验证
在URL地址中填入即可
url = \’http://用户名:密码@正常地址\’
示例: 爬取某一天笔记
from selenium import webdriver url = \'http://tarenacode:code_2013@code.tarena.com.cn/AIDCode/aid1904/15-spider/spider_day06_note.zip\' browser = webdriver.Chrome() browser.get(url)
iframe子框架
iframe子框架适用于网页中嵌套了网页,这种情况应该先切换到iframe子框架,然后再执行其他操作。
browser.switch_to.iframe(iframe_element)
示例 – 登录qq邮箱
import time from selenium import webdriver browser = webdriver.Chrome() browser.get(\'https://mail.qq.com/cgi-bin/loginpage\') # 找iframe子框架并切换到此iframe login_frame = browser.find_element_by_id(\'login_frame\') browser.switch_to.frame(login_frame) # qq+密码+登录 browser.find_element_by_id(\'u\').send_keys(\'账号\') browser.find_element_by_id(\'p\').send_keys(\'密码\') browser.find_element_by_id(\'login_button\').click() time.sleep(5) # 预留页面记载时间 # 提取数据 ele = browser.find_element_by_id(\'useralias\') print(ele.text)