一、 selenium 下载高清图片
1、版本介绍
python 3.7.4
selenium = 3.141.0
chromedriver=84.0.4147.30
2、保存 base64 图片格式代码
src="data:image/gif;base64,R0lGODlhMwAxAIAAAAAAAPyH5BAAAAAAALAAAAAAzADEAAAK8jI+pBr0PowytzotTtbm/DTqQ6C3hGX
ElcraA9jIr66ozVpM3nseUvYP1UEHF0FUUHkNJxhLZfEJNvol06tzwrgd
LbXsFZYmSMPnHLB+zNJFbq15+SOf50+6rG7lKOjwV1ibGdhHYRVYVJ9Wn
k2HWtLdIWMSH9lfyODZoZTb4xdnpxQSEF9oyOWIqp6gaI9pI1Qo7BijbF
ZkoaAtEeiiLeKn72xM7vMZofJy8zJys2UxsCT3kO229LH1tXAAAOw=="
提取出其中的base64地址(即 base64, 后的内容)然后base64解码,然后保存为图片
url='''R0lGODlhMwAxAIAAAAAAAPyH5BAAAAAAALAAAAAAzADEAAAK8jI+pBr0PowytzotTtbm/DTqQ6C3hGX
ElcraA9jIr66ozVpM3nseUvYP1UEHF0FUUHkNJxhLZfEJNvol06tzwrgd
LbXsFZYmSMPnHLB+zNJFbq15+SOf50+6rG7lKOjwV1ibGdhHYRVYVJ9Wn
k2HWtLdIWMSH9lfyODZoZTb4xdnpxQSEF9oyOWIqp6gaI9pI1Qo7BijbF
ZkoaAtEeiiLeKn72xM7vMZofJy8zJys2UxsCT3kO229LH1tXAAAOw=='''
import base64
img=base64.urlsafe_b64decode(url + '=' * (4 - len(url) % 4))
fh = open("imageToSave.png", "wb")
fh.write(img)
fh.close()
3、selenium 对某个元素截图保存 .screenshot()方法
由于Google网络的原因,一些外网的图片高清图加载失败,导致下载的是缩减图,这一部分数据通过截取详情图片的元素来解决。
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
import time
import os
import re
import requests
import urllib3
urllib3.disable_warnings() # 去除警告
options = webdriver.ChromeOptions()
# 增加无头
options.add_argument('--disable-gpu')
# 防止被网站识别,开发者模式
options.add_experimental_option('excludeSwitches', ['enable-automation'])
browser = webdriver.Chrome(options=options)
browser.maximize_window()
browser.implicitly_wait(10)
browser.get('https://www.google.com/search?q=%E6%BF%80%E5%87%B8&tbm=isch&hl=zh-CN&hl=zh-CN#imgrc=1lpdzdDLl6rN9M')
ele = browser.find_element_by_xpath('//*[@id="Sva75c"]/div/div/div[3]/div[2]/c-wiz/div[1]/div[1]/div/div[2]/a/img')
print(ele.get_attribute('src'))
# 截图保存
ele.screenshot('1.png')
4 、selenium 常常遇见的反爬虫几个策略
主要参考:
Selenium反反爬-滑块验证
Selenium 系列篇(六):反反爬篇
使用 mitmproxy + python 做拦截代理
如何突破网站对selenium的屏蔽
Selenium-webdriver绕开反爬虫机制的4种方法
网站对Selenium的屏蔽
5、爬高清图片的代码
缺点:慢,效果受网络的影响,现在没有更好的方案...
2个代码都未解决的问题:
① 使用 无头 浏览器在 ‘点击更多’ 的点击事件可能不能触发
② 使用 无头 浏览器在 缩减图片截图时有的图片截的图可能有点问题
希望有知道怎么回事或者怎么解决的老铁请留个言,感谢!
(1)高清、缩减、base64 格式图片
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.common.exceptions import ElementNotInteractableException
from selenium.webdriver.common.keys import Keys
from requests.adapters import HTTPAdapter
import argparse
import time
import os
import re
import requests
import urllib3
urllib3.disable_warnings() # 去除警告
class GoogleSpider():
def __init__(self, url, keyword, time_sleep, target_dir):
self.url = url
self.keyword = keyword
self.time_sleep = time_sleep # 等待高清图片加载的时间
self.target_dir = target_dir
self.img_tmp_dir = os.path.join(self.target_dir,
self.add_timestamp(self.keyword))
if not os.path.exists(self.img_tmp_dir):
os.makedirs(self.img_tmp_dir)
self.head = None # 浏览器是否存在头
self.implicitly_wait_time = 5 # 隐形等待时间
self.js_slide_wait = 30 # js 滑动窗口等待时间
def clean_str(self, str_):
"""
清除字符串为图片命名时不合规的字符
:return:
"""
str_ = re.sub('[^\u4e00-\u9fa5A-Za-z\d]', '', str_)
return str_
def add_timestamp(self, str_):
"""
为字符串添加时间搓,保证字符串唯一不重复性
:param str_: str
:return:
"""
assert isinstance(str_, str)
time_str = str(time.time()).replace('.', '')
str_ = str_ + time_str
return str_
def create_browser(self,head):
"""
创建一个浏览器的对象
:param headless: 是否使用无头浏览器
:return:
"""
self.head = head
options = webdriver.ChromeOptions()
# 增加无头
if not self.head:
options.add_argument('--headless')
# 无头浏览器设置窗口大小,主要是为了解决截屏时,如果窗口比较小,截屏时会获得较小的图片
options.add_argument("--window-size=4000,1600")
options.add_argument('--disable-gpu')
# 关闭自动测试状态显示 // 会导致浏览器报:请停用开发者模式
# window.navigator.webdriver还是返回True,当返回undefined时应该才可行。
options.add_experimental_option("excludeSwitches", ['enable-automation'])
# 关闭开发者模式
options.add_experimental_option("useAutomationExtension", False)
# 设置中文
options.add_argument('lang=zh_CN.UTF-8')
# 更换头部
options.add_argument(
'user-agent="Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.130 Safari/537.36"')
# 部署项目在linux时,其驱动会要求这个参数
options.add_argument('--no-sandbox')
browser = webdriver.Chrome(options=options)
if self.head:
browser.maximize_window()
# 设置执行js代码转换模式,反反爬虫检测 window.navigator.webdriver 属性换为 undefined
browser.execute_cdp_cmd("Page.addScriptToEvaluateOnNewDocument", {
"source": """Object.defineProperty(navigator, 'webdriver', {get: () => undefined})""",
})
return browser
def get_main_page_info(self, browser):
"""
获取主页面且拉至最底部,且获取所有图片详情页的链接地址
:return:
"""
browser.get(self.url)
browser.implicitly_wait(self.implicitly_wait_time)
# TODO 滑动到底的js代码
js = """
(function () {
var y = document.documentElement.scrollTop;
var step = 100;
window.scroll(0, y);
function f() {
if (y < document.body.scrollHeight) {
y += step;
window.scroll(0, y);
setTimeout(f, 100); //每两次间隔滑动的时间 100ms
}
else {
window.scroll(0, y);
document.title += "scroll-done";
}
}
setTimeout(f, 1000); // 执行函数等待时间 1000ms
})();
"""
# TODO 执行滑动到底的操作
browser.execute_script(js)
# 等待窗口滑动结束
for i in range(self.js_slide_wait):
print('\rJS窗口滑动中,请等待 {}s,现在是第 {}s,网络好可下调 '.format(self.js_slide_wait, i + 1), end='', flush=True)
time.sleep(1)
print()
# TODO 点击 ‘ 加载更多的选项 ’
try:
browser.find_element_by_xpath(
'//div[@id="islmp"]/.//div[@jsname="i3y3Ic"]/.//input[@class="mye4qd"]').send_keys(Keys.ENTER)
print('缓冲中...')
time.sleep(8) # ‘加载更多’ 缓冲
# TODO 执行点击加载更多继续滚动
browser.execute_script(js)
# 等待窗口滑动结束
for i in range(self.js_slide_wait):
print('\rJS加载更多图片窗口滑动中,请等待 {}s,现在是第 {}s,网络好可下调 '.format(self.js_slide_wait, i + 1), end='', flush=True)
time.sleep(1)
print()
except Exception as e:
print(e, '网络不畅 或 该网页没有更多内容选项 .... ')
# 获取图片详情页链接地址
div_infos = browser.find_elements_by_xpath(
"//div[@id='islmp']/.//div[contains(@class,'isv-r PNCib MSM1fd')]") # 结果是一个列表
self.reduceImg_num = len(div_infos)
if self.reduceImg_num == 0:
assert Exception('网络链接 {} 错误\n 获取主页面class属性已更改'.format(self.url))
print('一共获取到 {} 张缩减图'.format(self.reduceImg_num))
# 定义重复请求的链接列表,保存 【{url,img_name},...】二元组列表
self.repeat_urls = []
# TODO 通过缩减图 DIV 获取高清图片的地址
for idx, i in enumerate(div_infos):
try:
data_id = i.get_attribute('data-id')
except Exception as e:
logger.error(e)
logger.error('"data_id" 的属性值获取失败,可能网站已更改属性')
continue
# 图片详情页的 url 地址
img_url = self.url + '#imgrc=' + data_id
try:
browser.get(img_url)
self.target_img_url = browser.find_element_by_xpath(
"//div[@id='Sva75c']/.//div[@class='tvh9oe BIB1wf']/.//div[@class='zjoqD']/.//img").get_attribute(
'src')
self.target_img_name = browser.find_element_by_xpath(
"//div[@id='Sva75c']/.//div[@class='tvh9oe BIB1wf']/.//div[@class='zjoqD']/.//img").get_attribute(
'alt')
# 如果获取的url不以 data: 或者 http 开头,视为获取失败
if not (self.target_img_url.startswith('data:') or self.target_img_url.startswith('http')):
logger.info("第 {} 高清图片url: {} 格式错误,跳过".format(idx + 1, self.target_img_url))
continue
# 对是否详情页加载为高清图的url进行判断: https://encrypted-tbn0.gstatic.com/images? 开头属于缩减图
# 因此对详情页进行最长为 time_sleep 时间的等待,每次判断的时间step为250ms
if self.target_img_url.startswith('https://encrypted-tbn0.gstatic.com/images?'):
for step in range(time_sleep * 4):
if not self.target_img_url.startswith('https://encrypted-tbn0.gstatic.com/images?'):
break
time.sleep(0.25)
self.target_img_url = browser.find_element_by_xpath(
"//div[@id='Sva75c']/.//div[@class='tvh9oe BIB1wf']/.//div[@class='zjoqD']/.//img").get_attribute(
'src')
elif self.target_img_url.startswith('data:'):
for step in range(int(max(1.5, time_sleep - 3) * 4)): # data: 类型的图片,最少等待 1.5s 去加载图片
if not self.target_img_url.startswith('data:'):
break
time.sleep(0.25)
self.target_img_url = browser.find_element_by_xpath(
"//div[@id='Sva75c']/.//div[@class='tvh9oe BIB1wf']/.//div[@class='zjoqD']/.//img").get_attribute(
'src')
except Exception as e:
logger.error(e)
logger.error('当前第 {} 张缩减图片详情信息url获取信息失败,跳过处理'.format(idx + 1))
continue
# TODO 图片高清地址获取完毕,现在构建图片保存所需要的路径
self.target_img_name = self.add_timestamp(self.clean_str(self.target_img_name)) + '.jpg'
img_file_path = os.path.join(self.img_tmp_dir,
self.target_img_name)
# TODO 图片保存
if self.target_img_url.startswith(
'https://encrypted-tbn0.gstatic.com/images?') or self.target_img_url.startswith('data:'):
print(
'总量:{}, 图片序号为:{},正在保存【缩减图】至 {},图片名: {} url:{}'.format(self.reduceImg_num, idx, img_file_path,
self.target_img_name,
self.target_img_url[:150])
# data: 链接太长,这里做一个截取
)
else:
print(
'总量:{}, 图片序号为:{},正在保存【高清图】至 {},图片名: {} url:{}'.format(self.reduceImg_num, idx, img_file_path,
self.target_img_name,
self.target_img_url)
)
if self.target_img_url.startswith('http'):
self.save_url_img(img_file_path)
elif self.target_img_url.startswith('data:'):
self.save_base64_img(self.target_img_url, img_file_path)
# 重新爬取请求失败的高清图片的链接
repeat_urls = self.repeat_urls
self.repeat_spider(repeat_urls)
browser.quit() # 浏览器退出
def save_url_img(self, img_files_path):
"""
通过 url 请求保存图片
:param img_files_path: 给定要保存的文件名的路径
:return:
"""
try:
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.88 Safari/537.36',
}
# TODO 增加连接重试次数(一共2次请求链接)
# requests.adapters.DEFAULT_RETRIES = 1
sess = requests.Session()
sess.keep_alive = False # 关闭多余连接
text = sess.get(self.target_img_url, headers=headers, stream=True, verify=False, timeout=5)
with open(img_files_path, 'wb') as file:
for i in text.iter_content(1024 * 10):
file.write(i)
except Exception as e:
self.repeat_urls.append((self.target_img_url, img_files_path))
logger.error(e, self.target_img_url)
def save_base64_img(self, url, img_files_path):
"""
保存 base64 格式的图片
:param url:
:param img_files_path:
:return:
"""
import base64
url = re.sub('.+base64,', '', url)
img = base64.urlsafe_b64decode(url + '=' * (4 - len(url) % 4))
fh = open(img_files_path, "wb")
fh.write(img)
fh.close()
def screen_img(self, ele, img_files_path):
"""
给定需要截图的 element元素,进行截图
:param ele: 需要截图的元素
:param img_files_path: 图片的路径
:return:
"""
try:
ele.screenshot(img_files_path)
except Exception as e:
logger.error('一些原因导致截图失败,跳过处理', e)
def repeat_spider(self, urls, repeat_num=3):
"""
用于重复请求高清图片 get 失败的链接,下载图片
urls: 请求失败的 图片链接及其图片名称组成的二元组的列表
repeat_num: 重复循环最多 repeat_nume 次,裆列表为零时终止循环
"""
urls_num = len(urls)
logger.info('\n存在{}个链接请求失败,正在重新请求爬取,使用requests的方式请求:\n'.format(urls_num))
sucess_idx = [] # 保存成功的url 的索引列表
for num in range(repeat_num):
urls_num = len(urls)
if urls_num == 0:
break
for idx,url_info in enumerate(urls):
if idx not in sucess_idx:
url = url_info[0]
img_name = url_info[1]
try:
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.88 Safari/537.36',
}
# TODO 增加连接重试次数(一共4次链接)
sess = requests.Session()
sess.mount('http://', HTTPAdapter(max_retries=3))
sess.mount('https://', HTTPAdapter(max_retries=3))
sess.keep_alive = False # 关闭多余连接
text = requests.get(url, headers=headers, stream=True, verify=False, timeout=(4,5)) # connect 和 read 二者的 timeout
with open(img_name, 'wb') as file:
for i in text.iter_content(1024 * 10):
file.write(i)
text.close() # 关闭,很重要,确保不要过多的链接
logger.info('使用requests的方式请求,重复爬取阶段 保存成功 ,现在是第{}次循环,第{}张图片name-url:{}-{}'.format(num+1,idx+1,img_name,url))
# 保存成功的图片将成功的idx保存,确保下一次循环不会重新使用已成功的url
sucess_idx.append(idx)
except Exception as e:
logger.error(e)
logger.error('使用requests的方式请求,重复爬取请求失败:{}'.format(url))
time.sleep(0.5)
if __name__ == '__main__':
# TODO 参数
head = False
target_dir = r'target_dir'
time_sleep = 3 # 网速好的时间可以下调,加载高清最长等待时间
# logger.info(urls,head,keyword,target_dir)
urls = [
'https://www.google.com/search?q=风景&newwindow=1&sxsrf=ALeKk002XUAM8lwptPWsSjuox8SjA1B7_A:1600079585015&source=lnms&tbm=isch&sa=X&ved=2ahUKEwimsZv6uOjrAhUNkJQKHW5HBD4Q_AUoAXoECAwQAw&biw=1920&bih=937']
keyword = '风景'
for url in urls:
url = url.strip()
# TODO 运行
spider_obj = GoogleSpider(url, keyword, time_sleep, target_dir)
# 创建一个浏览器
browser = spider_obj.create_browser(head=head)
# 下载图片
spider_obj.get_main_page_info(browser=browser)
(2)使用截图代替下载 缩减图
tip:
1、删除:
from utils import utils
logger = utils.Logger_Hanlder.setup_logging(report_path='./log')
2、将所有的 logger语句转化为 print
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.common.exceptions import ElementNotInteractableException
from selenium.webdriver.common.keys import Keys
from requests.adapters import HTTPAdapter
import argparse
import time
import os
import re
import requests
import urllib3
urllib3.disable_warnings() # 去除警告
from utils import utils
logger = utils.Logger_Hanlder.setup_logging(report_path='./log')
class GoogleSpider():
def __init__(self,url,keyword,time_sleep,target_dir,repeat_num,max_imgNum = None):
self.url = url
self.keyword = keyword
self.repeat_num = repeat_num # 高清图片链接请求失败后重新爬取的次数
self.time_sleep = time_sleep # 等待高清图片加载的时间
self.max_imgNum = max_imgNum # 设置最大图片的下载量
self.target_dir = target_dir
self.img_tmp_dir = os.path.join(self.target_dir,
self.add_timestamp(self.keyword))
if not os.path.exists(self.img_tmp_dir):
os.makedirs(self.img_tmp_dir)
self.head = None # 浏览器是否存在头
self.implicitly_wait_time = 5 # 隐形等待时间
self.js_slide_wait = 30 # js 滑动窗口等待时间
def clean_str(self,str_):
"""
清除字符串为图片命名时不合规的字符
:return:
"""
str_ = re.sub('[^\u4e00-\u9fa5A-Za-z\d]','',str_)
return str_
def add_timestamp(self,str_):
"""
为字符串添加时间搓,保证字符串唯一不重复性
:param str_: str
:return:
"""
assert isinstance(str_,str)
time_str = str(time.time()).replace('.','')
str_ = str_ + time_str
return str_
def create_browser(self,head):
"""
创建一个浏览器的对象
:param headless: 是否使用无头浏览器
:return:
"""
self.head = head
options = webdriver.ChromeOptions()
# 增加无头
if not self.head:
options.add_argument('--headless')
# 无头浏览器设置窗口大小,主要是为了解决截屏时,如果窗口比较小,截屏时会获得较小的图片
options.add_argument("--window-size=4000,1600")
options.add_argument('--disable-gpu')
# 关闭自动测试状态显示 // 会导致浏览器报:请停用开发者模式
# window.navigator.webdriver还是返回True,当返回undefined时应该才可行。
options.add_experimental_option("excludeSwitches", ['enable-automation'])
# 关闭开发者模式
options.add_experimental_option("useAutomationExtension", False)
# 设置中文
options.add_argument('lang=zh_CN.UTF-8')
# 更换头部
options.add_argument(
'user-agent="Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.130 Safari/537.36"')
# 部署项目在linux时,其驱动会要求这个参数
options.add_argument('--no-sandbox')
browser = webdriver.Chrome(options=options)
if self.head:
browser.maximize_window()
# 设置执行js代码转换模式,反反爬虫检测 window.navigator.webdriver 属性换为 undefined
browser.execute_cdp_cmd("Page.addScriptToEvaluateOnNewDocument", {
"source": """Object.defineProperty(navigator, 'webdriver', {get: () => undefined})""",
})
return browser
def get_main_page_info(self,browser):
"""
获取主页面且拉至最底部,且获取所有图片详情页的链接地址
:return:
"""
browser.get(self.url)
browser.implicitly_wait(self.implicitly_wait_time)
# TODO 滑动到底的js代码
js = """
(function () {
var y = document.documentElement.scrollTop;
var step = 100;
window.scroll(0, y);
function f() {
if (y < document.body.scrollHeight) {
y += step;
window.scroll(0, y);
setTimeout(f, 100); //每两次间隔滑动的时间 100ms
}
else {
window.scroll(0, y);
document.title += "scroll-done";
}
}
setTimeout(f, 1000); // 执行函数等待时间 1000ms
})();
"""
# TODO 执行滑动到底的操作
browser.execute_script(js)
# 等待窗口滑动结束
for i in range(self.js_slide_wait):
print('\rJS窗口滑动中,请等待 {}s,现在是第 {}s,网络好可下调 '.format(self.js_slide_wait,i+1),end='',flush=True)
time.sleep(1)
print()
# TODO 点击 ‘ 加载更多的选项 ’
try:
browser.find_element_by_xpath('//div[@id="islmp"]/.//div[@jsname="i3y3Ic"]/.//input[@class="mye4qd"]').send_keys(Keys.ENTER)
print('缓冲中...')
time.sleep(8) # ‘加载更多’ 缓冲
# TODO 执行点击加载更多继续滚动
browser.execute_script(js)
# 等待窗口滑动结束
for i in range(self.js_slide_wait):
print('\rJS加载更多图片窗口滑动中,请等待 {}s,现在是第 {}s,网络好可下调 '.format(self.js_slide_wait, i + 1), end='', flush=True)
time.sleep(1)
print()
except Exception as e:
print(e, '网络不畅 或 该网页没有更多内容选项 .... ')
# 获取图片详情页链接地址
div_infos = browser.find_elements_by_xpath(
"//div[@id='islmp']/.//div[contains(@class,'isv-r PNCib MSM1fd')]") # 结果是一个列表
self.reduceImg_num = len(div_infos)
if self.reduceImg_num == 0:
assert Exception('网络链接 {} 错误\n 获取主页面class属性已更改'.format(self.url))
logger.info('一共获取到 {} 张缩减图,且图片下载给定的最大的数量为{}张'.format(self.reduceImg_num,self.max_imgNum))
# 定义重复请求的链接列表,保存 【{url,img_name},...】二元组列表
self.repeat_urls = []
# TODO 通过缩减图 DIV 获取高清图片的地址
for idx,i in enumerate(div_infos):
# 当图片下载超过给定的最大的数量时,停止爬取
if self.max_imgNum is not None:
if idx >= self.max_imgNum:
logger.info("当图片下载超过给定的最大的数量时,强制停止")
break
try:
data_id = i.get_attribute('data-id')
except Exception as e:
logger.error(e)
logger.error('"data_id" 的属性值获取失败,可能网站已更改属性')
continue
# 图片详情页的 url 地址
img_url = self.url + '#imgrc=' + data_id
try:
browser.get(img_url)
self.target_img_url = browser.find_element_by_xpath(
"//div[@id='Sva75c']/.//div[@class='tvh9oe BIB1wf']/.//div[@class='zjoqD']/.//img").get_attribute(
'src')
self.target_img_name = browser.find_element_by_xpath(
"//div[@id='Sva75c']/.//div[@class='tvh9oe BIB1wf']/.//div[@class='zjoqD']/.//img").get_attribute(
'alt')
# 如果获取的url不以 data: 或者 http 开头,视为获取失败
if not (self.target_img_url.startswith('data:') or self.target_img_url.startswith('http')):
logger.info("第 {} 高清图片url: {} 格式错误,跳过".format(idx+1 , self.target_img_url))
continue
# 对是否详情页加载为高清图的url进行判断: https://encrypted-tbn0.gstatic.com/images? 开头属于缩减图
# 因此对详情页进行最长为 time_sleep 时间的等待,每次判断的时间step为250ms
if self.target_img_url.startswith('https://encrypted-tbn0.gstatic.com/images?'):
for step in range(time_sleep * 4):
if not self.target_img_url.startswith('https://encrypted-tbn0.gstatic.com/images?'):
break
time.sleep(0.25)
self.target_img_url = browser.find_element_by_xpath(
"//div[@id='Sva75c']/.//div[@class='tvh9oe BIB1wf']/.//div[@class='zjoqD']/.//img").get_attribute(
'src')
elif self.target_img_url.startswith('data:'):
for step in range( int(max(1.5,time_sleep - 3) * 4) ): # data: 类型的图片,最少等待 1.5s 去加载图片
if not self.target_img_url.startswith('data:'):
break
time.sleep(0.25)
self.target_img_url = browser.find_element_by_xpath(
"//div[@id='Sva75c']/.//div[@class='tvh9oe BIB1wf']/.//div[@class='zjoqD']/.//img").get_attribute(
'src')
except Exception as e:
logger.error(e)
logger.error('当前第 {} 张缩减图片详情信息url获取信息失败,跳过处理'.format(idx+1))
continue
# TODO 图片高清地址获取完毕,现在构建图片保存所需要的路径
self.target_img_name = self.add_timestamp(self.clean_str(self.target_img_name)) + '.jpg'
img_file_path = os.path.join(self.img_tmp_dir,
self.target_img_name)
# TODO 图片保存
if self.target_img_url.startswith('https://encrypted-tbn0.gstatic.com/images?') or self.target_img_url.startswith('data:'):
logger.info(
'总量:{}, 图片序号为:{},正在保存【截图】至 {},图片名: {} url:{}'.format(self.reduceImg_num, idx, img_file_path,
self.target_img_name,
self.target_img_url[:150]) # data: 链接太长,这里做一个截取
)
# 保存截图
ele_img = browser.find_element_by_xpath(
"//div[@id='Sva75c']/.//div[@class='tvh9oe BIB1wf']/.//div[@class='zjoqD']/.//img")
self.screen_img(ele_img,img_file_path)
else:
logger.info(
'总量:{}, 图片序号为:{},正在保存【高清图】至 {},图片名: {} url:{}'.format(self.reduceImg_num, idx, img_file_path,
self.target_img_name,
self.target_img_url)
)
# 保存高清图
self.save_url_img(img_file_path)
time.sleep(0.5)
# 重新爬取请求失败的高清图片的链接
repeat_urls = self.repeat_urls
repeat_num = self.repeat_num
# self.repeat_spider_get(repeat_urls,repeat_num)
self.repeat_spider_screen(repeat_urls,repeat_num)
browser.quit() # 浏览器退出
def save_url_img(self,img_files_path):
"""
通过 url 请求保存图片
:param img_files_path: 给定要保存的文件名的路径
:return:
"""
try:
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.88 Safari/537.36',
}
# TODO 增加连接重试次数(一共2次请求链接)
# requests.adapters.DEFAULT_RETRIES = 1
sess = requests.Session()
sess.keep_alive = False # 关闭多余连接
text = sess.get(self.target_img_url, headers=headers, stream=True, verify=False, timeout=5)
with open(img_files_path, 'wb') as file:
for i in text.iter_content(1024 * 10):
file.write(i)
except Exception as e:
self.repeat_urls.append((self.target_img_url,img_files_path))
logger.error(e)
logger.error("爬取请求失败:{}".format(self.target_img_url))
def save_base64_img(self,url,img_files_path):
"""
保存 base64 格式的图片
:param url:
:param img_files_path:
:return:
"""
import base64
url = re.sub('.+base64,','',url)
img = base64.urlsafe_b64decode(url + '=' * (4 - len(url) % 4))
fh = open(img_files_path, "wb")
fh.write(img)
fh.close()
def screen_img(self,ele,img_files_path):
"""
给定需要截图的 element元素,进行截图
:param ele: 需要截图的元素
:param img_files_path: 图片的路径
:return:
"""
try:
ele.screenshot(img_files_path)
except Exception as e:
logger.error('一些原因导致截图失败,跳过处理',e)
def repeat_spider_get(self,urls,repeat_num):
"""
用于重复请求高清图片 get 失败的链接,下载图片
urls: 请求失败的 图片链接及其图片名称组成的二元组的列表
repeat_num: 重复循环最多 repeat_nume 次,裆列表为零时终止循环
"""
urls_num = len(urls)
logger.info('\n存在{}个链接请求失败,正在重新请求爬取,使用requests的方式请求:\n'.format(urls_num))
sucess_idx = [] # 保存成功的url 的索引列表
for num in range(repeat_num):
urls_num = len(urls)
if urls_num == 0:
break
fail_num = 0 # 判断连续失败的个数超过一定个数以后(本次设为6),终止重复爬取阶段,
for idx,url_info in enumerate(urls):
if idx not in sucess_idx:
url = url_info[0]
img_name = url_info[1]
try:
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.88 Safari/537.36',
}
# TODO 增加连接重试次数(一共4次链接)
sess = requests.Session()
sess.mount('http://', HTTPAdapter(max_retries=3))
sess.mount('https://', HTTPAdapter(max_retries=3))
sess.keep_alive = False # 关闭多余连接
text = requests.get(url, headers=headers, stream=True, verify=False, timeout=(4,5)) # connect 和 read 二者的 timeout
with open(img_name, 'wb') as file:
for i in text.iter_content(1024 * 10):
file.write(i)
text.close() # 关闭,很重要,确保不要过多的链接
logger.info('使用requests的方式请求,重复爬取阶段 保存成功 ,现在是第{}次循环,第{}张图片name-url:{}-{}'.format(num+1,idx+1,img_name,url))
# 保存成功的图片将成功的idx保存,确保下一次循环不会重新使用已成功的url
sucess_idx.append(idx)
fail_num = 0 # 将该数字归 0
except Exception as e:
logger.error(e)
logger.error('使用requests的方式请求,重复爬取请求失败:{}'.format(url))
# 判断连续失败的个数超过一定个数以后(本次设为6),终止重复爬取阶段,
fail_num += 1
if fail_num >= 6:
break
time.sleep(0.5)
def repeat_spider_screen(self,urls,repeat_num):
"""
用于重复请求高清图片 去 截图图片
urls: 请求失败的 图片链接及其图片名称组成的二元组的列表
repeat_num: 重复循环最多 repeat_nume 次,裆列表为零时终止循环
"""
urls_num = len(urls)
logger.info('\n存在{}个链接请求失败,正在重新请求爬取,使用浏览器截屏的方式来代替requests:\n'.format(urls_num))
head = self.head
browser = self.create_browser(head)
browser.implicitly_wait(5)
sucess_idx = [] # 保存成功的url 的索引列表
for num in range(repeat_num):
urls_num = len(urls)
if urls_num == 0:
break
fail_num = 0 # 判断连续失败的个数超过一定个数以后(本次设为6),终止重复爬取阶段,
for idx,url_info in enumerate(urls):
if idx not in sucess_idx:
url = url_info[0]
img_name = url_info[1]
try:
browser.get(url)
ele = browser.find_element_by_xpath('//img')
self.screen_img(ele = ele,img_files_path = img_name)
logger.info('使用浏览器截屏的方式,重复爬取阶段 保存成功 ,现在是第{}次循环,第{}张图片name-url:{}-{}'.format(num+1,idx+1,img_name,url))
# 保存成功的图片将成功的idx保存,确保下一次循环不会重新使用已成功的url
sucess_idx.append(idx)
fail_num = 0 # 将该数字归 0
except Exception as e:
logger.error(e)
logger.error('使用浏览器截屏的方式,重复爬取请求失败:{}'.format(url))
# 判断连续失败的个数超过一定个数以后(本次设为6),终止重复爬取阶段,
fail_num += 1
if fail_num >= 6:
break
time.sleep(0.5)
if __name__ == '__main__':
# argparse 回调函数
def str2bool(v):
if isinstance(v, bool):
return v
if v.lower() in ('yes', 'true', 't', 'y', '1'):
return True
elif v.lower() in ('no', 'false', 'f', 'n', '0'):
return False
else:
raise argparse.ArgumentTypeError('Boolean value expected.')
# TODO 参数
# 创建对象
parser = argparse.ArgumentParser()
# 添加参数
parser.add_argument('urls', help='main_page url',type=str,nargs='+') # url 地址,可以给多个
parser.add_argument('--keywords', help='keywords',nargs='+',type=str) # 关键字
parser.add_argument('--repeat_num', help='repeat spider num',type=int,default=1) # 高清图片url请求失败 重复获取的次数
parser.add_argument('--max_imgNums', help='each url max imgs num',nargs='+',type=int,default=400) # 保存图片的最大数量
parser.add_argument('--head', help='headless YES or NO',type=str2bool,default=True) # 是否开启无头浏览器
# 使用parse_args解析参数
args = parser.parse_args()
urls = args.urls
keywords = args.keywords
repeat_num = args.repeat_num
max_imgNums = args.max_imgNums
head = args.head
target_dir = r'C:\Users\liukang17\Desktop'
time_sleep = 3 # 网速好的时间可以下调,加载高清最长等待时间
logger.info("\n\n urls:{}-head:{}-keywords:{}-target_dir:{}-repeat_num:{}-max_imgNums:{} \n\n".format(urls,head,keywords,target_dir,repeat_num,max_imgNums))
logger.error("\n\n urls:{}-head:{}-keywords:{}-target_dir:{}-repeat_num:{}-max_imgNums:{} \n\n".format(urls,head,keywords,target_dir,repeat_num,max_imgNums))
tmp1_num = len(max_imgNums)
tmp2_num = len(keywords)
for idx,url in enumerate(urls):
url = url.strip()
# 判断 max_imgNums 与 keywords 列表索引是否越界
if idx >= tmp1_num:
max_imgNum = max_imgNums[-1]
else:
max_imgNum = max_imgNums[idx]
if idx >= tmp2_num:
keyword = keywords[-1]
else:
keyword = keywords[idx]
# TODO 运行
spider_obj = GoogleSpider(url,keyword,time_sleep,target_dir,repeat_num,max_imgNum)
# 创建一个浏览器
browser = spider_obj.create_browser(head=head)
# 下载图片
spider_obj.get_main_page_info(browser=browser)
执行脚本:
cd C:\Users\****\Desktop\LanJunTools
python google_image_main.py "https://www.google.com.tw/search?q=风景&source=lnms&tbm=isch&sa=X&ved=2ahUKEwijoaW6o7bsAhUEIIgKHUQPBeAQ_AUoAXoECBcQAw&biw=1366&bih=625" "https://www.google.com.tw/search?q=壁纸&tbm=isch&ved=2ahUKEwiRwNG7o7bsAhXENKYKHaUzBCcQ2-cCegQIABAA&oq=%E5%A3%81%E7%BA%B8&gs_lcp=CgNpbWcQAzICCAAyAggAMgIIADICCAAyBAgAEB4yBAgAEB4yBAgAEB4yBAgAEB4yBAgAEB4yBAgAEB46BAgAEAQ6BggAEAUQHlCqd1iGogFg16MBaABwAHgAgAGoA4gBiAqSAQkwLjMuMi4wLjGYAQCgAQGqAQtnd3Mtd2l6LWltZ7ABAMABAQ&sclient=img&ei=VBWIX5HGDcTpmAWl55C4Ag&bih=625&biw=1366" --max_imgNums 900 200 --head 0 --keywords 风景 壁纸
二、利用 selenium 自动上传文件(图片/文本)
1、利用 .send_keys( file_path ) 的方式上传 图片
selenium处理文件或图片上传弹窗的三种方式(input框,robot类,autoIT3工具)
selenium 对文本框的输入操作一般有两种形式,传统的是直接通过定位元素通过sendKeys()方法直接在文本框中输入信息。
但有时候我们可以通过xpath的方式将其进行定位,但却不能通过sendKeys()向文本框中输入文本信息。
这种情况下,也需要借助JavaScript 代码完成输入。
HTML 文本域
<textarea id="id" style="width: 98%" cols="50" rows="5" class="txtarea">
</textarea>
...
...
第一种:
driver.find_element_by_id('id').sendKeys("需要输入的内容");
第二种:
text = '文本域上传的内容'
js = "var kw = document.getElementById('kw') ;kw.value='{}';".format(text)
driver.execute_script(js)
三、自动上传大众点评头像、评论
Directory_Hanlder 自定义库代码地址:
失败,已放弃
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from selenium import webdriver
from utils.utils import Directory_Hanlder
from selenium.webdriver.common.keys import Keys
from selenium.common.exceptions import ElementNotInteractableException
import os #
import numpy as np
import pandas as pd
import time
import argparse
class DumpMassage():
def __init__(self,choice,head_img_dir = None, comments_signs_file = None):
# 选择哪种方式执行 'img' 上传头像 'text' 上传文本 'all' 同时上传
assert choice in ['img','text','all']
self.choice = choice
# 登录浏览器的参数
self.log_in_url = "https://account.dianping.com/login?" # 登录点评网页的链接地址
self.log_in_sleep = 15 # 登录页面扫码等待时间
self.main_page = 'https://www.dianping.com/'
# 上传头像图片的参数
self.head_img_url = 'http://www.dianping.com/member/myinfo/setup/userface' # 头像上传的链接地址
self.head_img_sleep = 2.5 # 上传图片等待时间提交,最小为 time_sleep,最大为 time_sleep + 1.5 的随机数
self.head_img_dir = head_img_dir # 上传头像图片的目录路径
# 评论与签名相关参数
self.comments_signs_file = comments_signs_file # 上传文本数据的文件路径
# 浏览器对象参数
self.head = True
self.implicitly_wait = 15
# 浏览器初始化
self.browser = self.create_browser(head=self.head)
self.browser.implicitly_wait(self.implicitly_wait)
self.browser = self.log_in_dianping(self.browser,self.log_in_url,time_sleep=self.log_in_sleep)
# 浏览器打开主页面 且 再打开一个窗口
# self.browser.get(self.main_page)
# self.browser.find_element_by_xpath("//a[@class= 'item left-split username J-user-trigger']").send_keys(Keys.ENTER)
self.browser.get(self.head_img_url) # 此时,浏览器其实已经有了两个窗口了{ 目前位于第一个窗口,即主窗口 },第一个窗口用来上传头像,第二个窗口用来上传文本数据
self.windows = self.browser.window_handles
def create_browser(self,head):
"""
创建一个浏览器的对象
:param headless: 是否使用无头浏览器
:return:
"""
self.head = head
options = webdriver.ChromeOptions()
# 增加无头
if not self.head:
options.add_argument('--headless')
# 无头浏览器设置窗口大小,主要是为了解决截屏时,如果窗口比较小,截屏时会获得较小的图片
options.add_argument("--window-size=4000,1600")
options.add_argument('--disable-gpu')
# 关闭自动测试状态显示 // 会导致浏览器报:请停用开发者模式
# window.navigator.webdriver还是返回True,当返回undefined时应该才可行。
options.add_experimental_option("excludeSwitches", ['enable-automation'])
# 关闭开发者模式
options.add_experimental_option("useAutomationExtension", False)
# 设置中文
options.add_argument('lang=zh_CN.UTF-8')
# 更换头部
options.add_argument(
'user-agent="Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.130 Safari/537.36"')
# 部署项目在linux时,其驱动会要求这个参数
options.add_argument('--no-sandbox')
browser = webdriver.Chrome(options=options)
if self.head:
browser.maximize_window()
# 设置执行js代码转换模式,反反爬虫检测 window.navigator.webdriver 属性换为 undefined
browser.execute_cdp_cmd("Page.addScriptToEvaluateOnNewDocument", {
"source": """Object.defineProperty(navigator, 'webdriver', {get: () => undefined})""",
})
return browser
def log_in_dianping(self,browser,url,time_sleep = 15):
"""
浏览器登录点评
:param browser:
:param url: 登录点评的链接
:param time_sleep: 登录扫码等待的时间
:return:
"""
# 浏览器登录
browser.get(url) # 获取路径
time.sleep(0.3)
browser.switch_to.frame((browser.find_element_by_xpath('//iframe')))
browser.execute_cdp_cmd("Page.addScriptToEvaluateOnNewDocument", {
"source": """Object.defineProperty(navigator, 'webdriver', {get: () => undefined})""",
})
browser.find_element_by_xpath('//span[@class="bottom-password-login"]').click()
time.sleep(0.3)
browser.find_element_by_id('tab-account').click()
# 登录
browser.find_element_by_id('account-textbox').clear()
browser.find_element_by_id('account-textbox').send_keys('***********')
time.sleep(2)
browser.find_element_by_id('password-textbox').clear()
browser.find_element_by_id('password-textbox').send_keys('***********') # password
time.sleep(1)
browser.find_element_by_id('login-button-account').click()
# 登录等待
time.sleep(3)
return browser
def load_head_img_paths(self,img_dir):
"""
给定头像图片目录路径,加载所有图片的路径
:param img_dir:
:return:
"""
assert img_dir is not None
img_paths,_ = Directory_Hanlder.list_dir_all_files(img_dir)
img_nums = len(img_paths)
self.img_paths = img_paths
self.img_nums = img_nums
return img_paths,img_nums
def load_comments_sign(self,comments_path):
"""
加载文件【①去除空行数据 ②去除首尾空格】至Series中,保存每段文本所对应的行号
:param comments_path: 文件的路径
:return: Series
"""
assert comments_path is not None
# 将数据保存至 Series 中
comments_ser = pd.Series()
try:
with open(comments_path,'r',encoding='utf-8-sig') as w:
for row,line in enumerate(w.readlines()):
# 对每一行的数据前后去除空格
line = line.strip()
# 判断去除空格后的数据是否有内容,只要有数据的内容【去除空行数据】
if line:
# 添加行号
row_index = row + 1
comments_ser.loc[row_index] = line
except Exception as e:
with open(comments_path, 'r', encoding='gbk') as w:
for row, line in enumerate(w.readlines()):
# 对每一行的数据前后去除空格
line = line.strip()
# 判断去除空格后的数据是否有内容,只要有数据的内容【去除空行数据】
if line:
# 添加行号
row_index = row + 1
comments_ser.loc[row_index] = line
return comments_ser,len(comments_ser)
def dump_head_img(self,url,browser,img_path,time_sleep=1.5):
"""
上传头像图片的函数,给定 一张头像图片地址进行上传,
:param url:
:param time_sleep: 上传图片等待时间提交, 最小为 time_sleep
:return:
"""
try:
browser.find_element_by_xpath("//input[@capture='camera']").send_keys(img_path)
except:
# 当找不到元素的时候,重新请求一下页面
browser.get(url)
# 获取上传头像 ele 元素
browser.find_element_by_xpath("//input[@capture='camera']").send_keys(img_path)
# 上传图片等待时间提交,最小为 time_sleep
tmp1_num = np.random.uniform(time_sleep, time_sleep + 1.5)
time.sleep(tmp1_num)
# 点击提交 ---- 使用 js 大法
flag = True
try:
js = 'document.getElementById("J_usave").click();'
browser.execute_script(js)
# 判断url并非最初的美团图片,才进行打印
dump_url = browser.find_element_by_xpath("//img[@id='J_bface']").get_attribute('src')
if not dump_url.startswith('https://p0.meituan.net/userheadpic/chicken.png%40120w'):
print('随机数等待上传图片时间为: {:.1f} , 上传的图片url为:{}'.format(tmp1_num,dump_url))
if len(self.browser.find_elements_by_class_name('medi-btn-ashdisb')) == 0:
print('找不到button的类属性 medi-btn-ashdisb ,判断图片点击失败')
flag = False
except Exception as e:
flag = False
print('\nERROR: \n',e)
print('当前图片点击事件触发失败,上传失败,已跳过\n')
# 上传后等待 1.6 - 2.5 s
time.sleep(np.random.uniform(1.6,2.5))
time.sleep(100)
return browser,flag
def dump_text(self,line,browser):
"""
上传文本
:return:
"""
url = "http://www.dianping.com/member/myinfo/setup/basic"
browser.get(url)
# TODO 给文本域上传文本
js = "var kw = document.getElementById('J_sign') ;kw.value='{}';".format(line)
browser.execute_script(js)
time.sleep(1)
# js = 'document.getElementsByClassName("btn-txt J_submit")[0].click();'
browser.execute_script(js)
return browser
def run_img(self,browser):
"""
仅仅自动上传头像
:param browser: 浏览器对象
:return:
"""
self.browser = browser
# 获取 给定的头像目录路径 所有图片的路径
img_paths,img_num = self.load_head_img_paths(self.head_img_dir)
print('一共有 {} 张头像需要上传, 现在正在上传中....'.format(img_num))
# 浏览器请求头像页面链接
self.browser.get(self.head_img_url)
for idx,head_img in enumerate(img_paths):
# 每 10 张 图片重新请求下头像上传页面,防止假死状态
if (idx+1) % 10 == 0:
self.browser.get(self.head_img_url)
time.sleep(np.random.uniform(0.8,1.4))
# 防止后台检测,每上传25 张图片, 睡眠 60s 的时间
if (idx + 1) % 25 == 0:
for i in range(60):
time.sleep(1)
print('\r等待 60s 继续上传头像,现在是第{}s '.format(i+1),end='',flush=True)
print()
# 重新请求头像上传的网址
self.browser.get(self.head_img_url)
time.sleep(np.random.uniform(0.8, 1.4))
# 上传头像
self.browser,flag = self.dump_head_img(self.head_img_url,self.browser,head_img,time_sleep=self.head_img_sleep)
if not flag:
continue
elif flag:
print('第 {} 张头像图片-{} 上传成功!'.format(idx+1,head_img))
print('头像 {} 张图片已全部上传完毕!'.format(img_num))
return self.browser
def run_comments(self):
""""""
# 更换第二个窗口
self.browser.switch_to.window(self.windows[-1])
# 执行 js 更换 window.navigator.webdriver 属性
self.browser.execute_cdp_cmd("Page.addScriptToEvaluateOnNewDocument", {
"source": """Object.defineProperty(navigator, 'webdriver', {get: () => undefined})""",
})
# 获取需要所有的文本数据
comments,num = self.load_comments_sign(self.comments_signs_file)
print(comments,num)
for idx,line in enumerate(comments):
# 上传文本数据
self.browser = self.dump_text(line,self.browser)
print('共 {} 条文本,第 {} 条内容: {} '.format(num,idx+1,line))
time.sleep(2)
return self.browser
def run_all(self):
pass
def run(self):
"""
主函数,运行
:return:
"""
if self.choice is 'img':
self.run_img(self.browser)
elif self.choice is 'text':
self.run_comments()
elif self.choice is 'all':
self.run_all()
else:
raise Exception("choice 参数输入有误! 从 ['img','text','all'] 选择一个 ")
if __name__ == '__main__':
head_img_dir = r'C:\Users\Hasee\Desktop\touxiang'
comments_sign = r'C:\Users\Hasee\Desktop\作文.txt'
choice = 'text'
obj = DumpMassage(choice = choice,head_img_dir = head_img_dir,comments_signs_file=comments_sign)
browser = obj.run()
#退出浏览器
browser.quit()