爬虫与反爬虫的斗争
爬虫建议
-
尽量减少请求次数
- 保存获取到的HTML,供查错和重复使用
-
关注网站的所有类型的页面
- H5页面
- APP
-
多伪装
- 代理IP(基本不怎么使用)
获取随机的请求头:
代码实例:
from fake_useragent import UserAgent
ua = UserAgent() # 实例化 UserAgent
for i in range(10):
headers = {
'User-Agent': ua.random # 随机取出一个 UserAgent
}
print(headers)
Ajax基本介绍
动态了解HTML技术:
-
JavaScript
- 是网络上最常用的脚本语言,它可以收集用户的跟踪数据,不需要重载页面直接提交表单,在页面嵌入多媒体文件,甚至运行网页
-
jQuery
- jQuery是一个快速、简洁的JavaScript框架,封装了JavaScript常用的功能代码
-
Ajax
- Ajax可以使用网页实现异步更新,可以在不重新加载整个网页的情况下,对网页的某部分进行更新
获取Ajax数据的方式
- 直接分析Ajax调用的接口。然后通过代码请求这个接口。
- 使用 Selenium+chromedriver 模拟浏览器行为获取数据。
Selenium+chromedriver
selenium介绍:
-
selenium 是一个web的自动化测试工具,最初是为网站自动化测试而开发的,selenium 可以直接运行在浏览器上,它支持所有主流的浏览器,可以接收指令,让浏览器自动加载页面,获取需要的数据,甚至页面截屏
-
chromedriver是一个驱动Chrome浏览器的驱动程序,使用他才可以驱动浏览器。当然针对不同的浏览器有不同的driver。以下列出了不同浏览器及其对应的driver:
-
下载chromedriver
-
安装 Selenium
pip install selenium
Selenium快速入门:
import time
from selenium import webdriver
# 实例化浏览器
driver = webdriver.Chrome()
# 发送请求
driver.get('https://www.baidu.com')
time.sleep(5)
# 退出浏览器
driver.quit()
注意:如果是高版本的 Selenium,程序运行完会立即关闭浏览器窗口。解决:可以通过 time.sleep()设置延迟后再关闭,或者选择低版本的 Selenium。
或者:
from selenium.webdriver import Chrome
chromedriver = "/usr/local/bin/chromedriver"
web = Chrome()
web.get('https://www.baidu.com')
driver 定位元素之代码实例:
import time
from selenium import webdriver
from selenium.webdriver.common.by import By
driver = webdriver.Chrome()
driver.get('http://www.baidu.com')
# 通过 id 值定位
driver.find_element(By.ID, 'kw').send_keys('生产者与消费者模式')
driver.find_element(By.ID, 'su').click()
time.sleep(3)
# 通过 class 值定位
driver.find_element(By.CLASS_NAME, 's_ipt').send_keys('生产者与消费者模式')
time.sleep(3)
# 通过 name 定位
driver.find_element(By.NAME, 'wd').send_keys('生产者与消费者模式')
# 通过 tag_name 定位
tagName = driver.find_elements(By.TAG_NAME, 'input') # 查找所有input标签
print(tagName)
# 通过 xpath 语法定位
driver.find_element(By.XPATH, '//*[@id="kw"]').send_keys('元森')
# 通过 css 语法定位
driver.find_element(By.CSS_SELECTOR, '#kw').send_keys('可口可乐')
# 通过文本定位 - 精准
driver.find_element(By.LINK_TEXT, '图片').click()
driver 操作表单元素之代码实例:
import time
from selenium import webdriver
from selenium.webdriver.support.ui import Select
from selenium.webdriver.common.by import By
driver = webdriver.Chrome()
url = 'https://kyfw.12306.cn/otn/regist/init'
driver.get(url)
"""定位到 select 标签,实例化 select 对象"""
selectTag = Select(driver.find_element(By.ID, 'cardType'))
time.sleep(2)
# 根据属性支定位
selectTag.select_by_value('C')
time.sleep(2)
# 根据索引定位(从0开始)
# selectTag.select_by_index(2)
Selenium 模拟登录豆瓣之代码实例:
from selenium import webdriver
from selenium.webdriver.common.by import By
import time
"""模拟登录豆瓣"""
# 加载驱动
driver = webdriver.Chrome()
# 拿到目标url
driver.get('https://www.douban.com/')
""" iframe 是 HTML 的一个标签,作用:文档中的文档
如果要找的标签元素被 iframe 标签嵌套,需要切换 iframe
"""
# 切换 iframe
login_iframe = driver.find_element(By.XPATH, '//*[@id="anony-reg-new"]/div/div[1]/iframe')
driver.switch_to.frame(login_iframe) # 包含要切换焦点的所有选项的对象
time.sleep(2)
# 1、切换登录方式,定位到 密码登录(这一步之前要确定是否要切换 iframe)
driver.find_element(By.CLASS_NAME, 'account-tab-account').click()
time.sleep(1)
# 2、输入账号密码
user_input = driver.find_element(By.ID, 'username').send_keys('maria')
time.sleep(1)
pwd_input = driver.find_element(By.ID, 'password').send_keys('123456')
time.sleep(2)
# 3、点击登录豆瓣
# 定位登录按钮点击,如果在定位元素时,属性出现空格的状态,一般是选择属性当中的一部分
driver.find_element(By.CLASS_NAME, 'btn-account ').click()
time.sleep(2)
鼠标行为链
有时候在页面中的操作可能要有很多步,那么这时候可以使用鼠标行为链类ActionChains来完成。比如现在要将鼠标移动到某个元素上并执行点击事件。
actions = ActionChains(driver)
actions.move_to_element(inputTag)
actions.send_keys_to_element(inputTag,'python')
actions.move_to_element(submitTag)
actions.context_click()
actions.click(submitTag)
actions.perform()
还有更多的鼠标相关的操作:
- click_and_hold(element):点击但不松开鼠标。
- context_click(element):右键点击。
- double_click(element):双击。
- 更多方法请参考:selenium-python.readthedocs.io/api.html
练习:
from selenium import webdriver
from selenium.webdriver import ActionChains
from selenium.webdriver.common.by import By
import time
driver = webdriver.Chrome() # 加载驱动
driver.get('https://www.baidu.com') # 拿到目标URL
inputTag = driver.find_element(By.ID, 'kw') # 定位到百度输入框
buttonTag = driver.find_element(By.ID, 'su') # 定位到百度按钮
actions = ActionChains(driver) # 实例化一个鼠标行为链的对象
actions.send_keys_to_element(inputTag, 'python') # 在已经定位好的输入框输入内容
time.sleep(1)
# 第一种方法
# 注意你用的逻辑操作和鼠标行为链没有相关 那么这些个操作需要放到perform()的外面
# buttonTag.click()
# 第二张方法 在鼠标行为链中进行操作,没有问题
actions.move_to_element(buttonTag)
actions.click(buttonTag)
actions.perform() # 提交行为链的操作
time.sleep(2)
Cookie操作
- 获取所有的cookie
cookies = driver.get_cookies()
- 根据 cookie 的 name 获取 cookie
value = driver.get_cookie(name)
- 删除某个cookie
driver.delete_cookie('key')
selenium携带cookie登录qq空间
from selenium import webdriver
from selenium.webdriver.common.by import By
import time
import requests
# 模拟登录qq空间
# 加载驱动
driver = webdriver.Chrome()
driver.get('https://xui.ptlogin2.qq.com/cgi-bin/xlogin?proxy_url=https%3A//qzs.qq.com/qzone/v6/portal/proxy.html&daid'
'=5&&hide_title_bar=1&low_login=0&qlogin_auto_login=1&no_verifyimg=1&link_target=blank&appid=549000912'
'&style=22&target=self&s_url=https%3A%2F%2Fqzs.qzone.qq.com%2Fqzone%2Fv5%2Floginsucc.html%3Fpara%3Dizone'
'&pt_qr_app=%E6%89%8B%E6%9C%BAQQ%E7%A9%BA%E9%97%B4&pt_qr_link=http%3A//z.qzone.com/download.html'
'&self_regurl=https%3A//qzs.qq.com/qzone/v6/reg/index.html&pt_qr_help_link=http%3A//z.qzone.com/download'
'.html&pt_no_auth=0')
# 定位头像图片
imgTag = driver.find_element(By.CLASS_NAME, 'face')
imgTag.click()
time.sleep(2)
# 获取cookie
cookie = driver.get_cookies() # 返回一个列表
# print(cookie, type(cookie))
# 通过for循环打印,发现它是一个列表,里面存放的是字典格式的数据
# for i in cookie:
# print(i, type(i))
cookie = [item['name'] + '+' + item['value'] for item in cookie]
cookieStr = '; '.join(cookie)
print(cookieStr)
time.sleep(2)
# 目标URL
url = 'https://user.qzone.qq.com/1142667439'
headers = {
"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) "
"Chrome/114.0.0.0 Safari/537.36",
'cookie': cookieStr
}
response = requests.get(url, headers=headers)
time.sleep(1)
with open('qq空间.html', mode='w', encoding='utf-8', newline='') as f:
f.write(response.text)
print(response.text)
页面等待:
现在的网页越来越多采用了 Ajax 技术,这样程序便不能确定何时某个demo元素完全加载出来了。如果实际页面等待时间过长导致某个元素还没出来,但是如果代码直接使用 WebElement,那么就会抛出 NullPointer 的异常。为了解决这个问题。Selenium 提供了三种等待方式:一种是隐式等待、一种是显式等待。
强制等待:【走到某一步,强制执行等待,效率最低】
time.sleep()
隐式等待:调用 driver.implicitly_wait。那么在获取不可用的元素之前,会先等待10秒钟的时间。
driver.implicitly_wait(10)
显示等待:【最常用的,一旦满足条件,就继续往下执行】
表明某个条件成立后才执行获取元素的操作。也可以在等待的时候指定一个最大的时间,如果超过这个时间那么就抛出一个异常。显示等待应该使用selenium.webdriver.support.excepted_conditions 期望的条件和 selenium.webdriver.support.ui.WebDriverWait 来配合完成。
代码实例:
from selenium import webdriver
from selenium.webdriver.common.by import By
import time
# 加载驱动
driver = webdriver.Chrome()
driver.get('https://www.baidu.com/')
"""强制等待:
相当于阻塞当前线程,爬虫效率较低,不建议过多使用,会严重影响脚本性能
"""
# 定位输入框
# inputTag = driver.find_element(By.ID, 'kw').send_keys('python')
# time.sleep(3)
"""隐式等待:
只要找到元素就会立即执行,如果找不到会等待;
好处:只需设置一次,全局生效。如果超时时间内网页完成了全部加载,则立即进行下面的操作。
劣势:需要等待网页所有元素都加载完成后才执行下面的操作,
如果需要操作的元素提前加载好了,但是其它无关紧要的元素还没加载完成,那么会浪费时间去等待其它元素加载完成。
"""
# driver.implicitly_wait(6)
# btnTag = driver.find_element(By.ID, 'su')
# btnTag.click()
"""显示等待:
指定某个条件,然后设置最长等待时间
如果在这个时间内还没找到元素,便会抛出异常,只有当条件满足时才会执行后面的代码
好处:解决了隐式等待的不足
缺点:稍微复杂一点,需要学习成本
"""
from selenium.webdriver.support import expected_conditions as EC # 核心
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.common.by import By
driver.get('https://www.baidu.com/')
try:
# 直到指定元素加载出来,我只等待5秒
element = WebDriverWait(driver, 5).until(
# presence_of_element_located:某个元素已经加载完毕了
# EC.presence_of_all_elements_located():网页中所有满足条件的元素都加载完成
# EC.element_to_be_clickable():某个元素可以点击了
EC.presence_of_element_located((By.XPATH, '//*[@id="su"]'))
)
text = driver.page_source # element的源代码
print(text)
finally:
driver.quit()
一些其他的等待条件:
- presence_of_element_located:某个元素已经加载完毕了。
- presence_of_all_elements_located:网页中所有满足条件的元素都加载完毕了。
- element_to_be_clickable:某个元素是可以点击了。
更多条件请参考:selenium-python.readthedocs.io/waits.html
操作多窗口与页面切换:
切换界面:
-
[0] 代表的第一个最开始打开的那个,
-
[1] 代表第二个
-
[-1] 切换到最新打开的窗口
-
[-2] 倒数第二个打开的窗口
# 切换界面
driver.switch_to.window(driver.window_handles[1])
切换至iframe:
# 可以通过By.ID,By.CLASS_NAME...定位
login_iframe = driver.find_element(By.XPATH, '//*[@id="anony-reg-new"]/div/div[1]/iframe')
driver.switch_to.frame(login_iframe)
selenium 执行 js 语句:
有时候 selenium 提供的方法会有一些问题,或者执行起来比较麻烦, 这时使用通过 selenium 执行 js 会简单些。
语法:
execute_script(script, *args)
描述:用来执行js语句
参数 script:待执行的 js 语句,如果有多个js语句,使用英文分号;连接
1、滚动页面:
driver.execute_script('window.scrollTo(0, document.body.scrollHeight)')
2、js点击:
btn = driver.find_element(By.CLASS_NAME, 'my_button')
driver.execute_script('arguments[0].click()', btn)
3、等待元素出现后,再进行交互
element = driver.find_element(By.css_selector,'#my-element')
driver.execute_script('''
var wait = setInterval(function(){
if (arguments[0].offsetParent != null){
clearInterval(wait);
# 元素现在可见,可以对其进行操作
}
}, 100);
''', element)
4、执行自定义 js:
使用 execute_script 方法执行编写的任何自定义的js代码,以完成测试或自动化任务。
driver.execute_script('console.log("hello, world");')
5、打开多窗口
driver.execute_script('window.open("https://www.douban.com")')
selenium 高级语法:
- page_source:获取 element 源代码
- find():在 element 源代码中寻找某个字符串是否存在
- find_element(By.LINK_TEXT):根据链接文本获取,一般处理翻页问题
- node.get_attribute:node 代表节点名,get_attribute 代表获取的属性名
- node.text():获取节点的文本内容,包含子节点和后代节点
# page_source:获取 element 源代码
html = driver.page_source
print(html)
"""find():在html源码中查找某个字符是否存在
如果存在,则返回一段数字
若不存在,不会报错,返回 -1
使用场景:翻页爬取
"""
print(html.find('kw')) # 找得到,则返回一段数字
"""find_element(By.LINK_TEXT, '链接文本')"""
driver.find_element(By.LINK_TEXT, '下一页').click() # 跳转到下一页
"""node.get_attribute('属性名'),node代表你想获取的节点"""
# url = 'https://movie.douban.com/top250'
# driver.get(url)
# a_tag = driver.find_element(By.XPATH, '//div[@class="item"]/div[@class="pic"]/a')
# print(a_tag.get_attribute('href'))
"""node.text 获取节点的文本内容,包含子节点和后代节点"""
driver.get('https://movie.douban.com/top250')
time.sleep(2)
div_tag = driver.find_element(By.XPATH, '//div[@class="hd"]')
print(div_tag.text)
selenium 设置界面模式:
绝大多数服务器是没有界面的,selenium 控制谷歌浏览器也是存在无界面模式的,简称为无头模式
options = webdriver.ChromeOptions() # 开启无界面模式
options.add_argument("--headless") # 配置对象添加开启无界面模式的命令
driver = webdriver.Chrome(options=options) # 实例化带有配置对象的 driver 对象
driver.get('http://www.baidu.com/')
html = driver.page_source # page_source:获取 element 源代码
print(html)
time.sleep(2)
driver.quit()
selenium 被识别问题 解决方案:
selenium 做爬虫能解决很多反爬问题,但是 selenium 也有很多特征可以被识别, 比如用 selenium 驱动浏览器后 window.navigator.webdriver 值是 true,而正常运行浏览器该值是未定义的(undefined)。
from selenium import webdriver
import time
# 使用 Chrome 开发者模式
options = webdriver.ChromeOptions()
options.add_experimental_option('excludeSwitches', ['enable-automation'])
# 禁用启用 Blink 运行时的功能
options.add_argument("--disable-blink-features=AutomationControlled")
# selenium执行cdp命令,再次覆盖window.navigator.webdriver的值
driver = webdriver.Chrome(options=options)
driver.execute_cdp_cmd("Page.addScriptToEvaluateOnNewDocument", {
"source": """
Object.defineProperty(navigator, 'webdriver', {
get: () => undefined
})
"""
})
driver.get('https://www.baidu.com/')
time.sleep(2)
综合案例:爬取部分京东的商品数据
from selenium import webdriver
from selenium.webdriver.common.by import By
import time
"""
需求:
拿到对应的价格/书名/评价数量/店铺或者出版社 并将数据保存到csv文件中
第一步 页面结构分析
通过分析每一本书都是一个li标签 那么这页所有的数据都是在一个ul标签里面
京东的数据页面 上来回加载一部分数据(30个) 剩下的一部分数据 通过拖动滚轮然后缓缓的加载出来 (60个)
思路分析:
1 先打开网站 https://www.jd.com/
2 输入内容(爬虫书)
3 点击搜索
4 想办法把滚轮拖动到最后
5 爬取数据/解析数据
6 翻页处理 pn-next disabled
代码实现:
"""
class jd_spider:
# 初始化
def __init__(self):
self.driver = webdriver.Chrome() # 加载驱动
self.driver.get('https://www.jd.com/') # 拿到目标url
input_tag = self.driver.find_element(By.XPATH, '//*[@id="key"]')
input_tag.send_keys('爬虫书')
time.sleep(2)
btn_tag = self.driver.find_element(By.XPATH, '//*[@id="search"]/div/div[2]/button')
btn_tag.click()
time.sleep(2)
# 解析数据,定义函数
def parse_html(self):
# 滑动到页面底部
self.driver.execute_script(
'window.scrollTo(0,document.body.scrollHeight)'
)
time.sleep(2)
# 通过抓包定位到商品信息,全部存储在ul标签中的li标签中
liList = self.driver.find_elements(By.XPATH, '//*[@id="J_goodsList"]/ul/li')
for li in liList:
try:
# 定义一个字典,用于存储拿到的数据
item = {}
# 通过xpath定位拿到想要的数据
item['price'] = li.find_element(By.XPATH, './/div[@class="p-price"]/strong').text.strip()
item['book_name'] = li.find_element(By.XPATH, './/div[@class="p-name"]/a/em').text.strip()
item['reviews_num'] = li.find_element(By.XPATH, './/div[@class="p-commit"]/strong').text.strip()
item['book_name'] = li.find_element(By.XPATH, './/div[@class="p-shopnum"]/a').text.strip()
print(item)
except Exception as e:
print(e)
def next_html(self):
while True:
self.parse_html()
if self.driver.page_source.find('pn-next disabled') == -1: # 找不到,不存在时返回-1
next_tag = self.driver.find_element(By.XPATH, '//*[@id="J_bottomPage"]/span[1]/a[9]')
next_tag.click()
else:
self.driver.quit()
break
if __name__ == '__main__':
spider = jd_spider()
spider.next_html()