headless浏览器每次操作时耗时最多的是启动的时候,因此可以创建一个全局的浏览器对象,在需要操作的时候,操作这个对象即可
实现步骤
- 编写模板网页
- 生成JS代码
- 注入JS代码
- 网页截图
在这里我以唐块儿机器人的物价图片为例,效果图如下
编写模板网页
在这里我喜欢Markdown的样式,GitHub地址:https://github.com/sindresorhus/github-markdown-css,下载之后在HTML模板文件中导入样式,把需要Markdown样式的标签放到标签内,即可让原来的p、h1、table等标签拥有Markdown的样式。要注意的是,需要注入数据的地方最好都编写上id,方便注入数据。HTML模板网页代码如下,我设置了全局字体为苹方字体、关闭滚动条
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1, minimal-ui">
<title>Price template</title>
<link rel="stylesheet" href="css/github-markdown.css">
<style>
*:not([class*='icon']):not(.fa):not(.fas):not(i) {
font-family: '思源宋体 CN Medium', 'PingFang SC', 'Heiti SC', 'myfont', 'Microsoft YaHei', 'Source Han Sans SC', 'Noto Sans CJK SC', 'HanHei SC', 'sans-serif', 'icomoon', 'Icons', 'brand-icons', 'FontAwesome', 'Material Icons', 'Material Icons Extended', 'Glyphicons Halflings' !important;
}
* {
font-weight: !important;
font-family: '思源宋体 CN Medium', 'PingFang SC', 'Microsoft YaHei', serif;
}
body::-webkit-scrollbar {
display: none;
}
body {
box-sizing: border-box;
min-width: 620px;
max-width: 620px;
min-height: 780px;
max-height: 780px;
margin: 0 auto;
padding: 25px;
}
</style>
</head>
<body>
<article class="markdown-body">
<table>
<thead>
<tr>
<th id="price_title" colspan="2" align="center"></th>
</tr>
</thead>
<tbody>
<tr>
<th id="region_1" align="center">电点</th>
<th id="region_3" align="center">电一</th>
</tr>
<tr>
<td id="region_1_data" align="center" style="align-content: center" rowspan="3">该区服没有数据,哎?Σ(⊙▽⊙")</td>
<td id="region_3_data" align="center" style="align-content: center">该区服没有数据,哎?Σ(⊙▽⊙")</td>
</tr>
<tr>
<th align="center">双四</th>
</tr>
<tr>
<td id="region_5_data" align="center" style="align-content: center">该区服没有数据,哎?Σ(⊙▽⊙")</td>
</tr>
<tr>
<th align="center">双一</th>
<th align="center">双二</th>
</tr>
<tr>
<td id="region_2_data" align="center" style="align-content: center">该区服没有数据,哎?Σ(⊙▽⊙")</td>
<td id="region_4_data" align="center" style="align-content: center">该区服没有数据,哎?Σ(⊙▽⊙")</td>
</tr>
</tbody>
</table>
<p style="width:100%;font-size:16px;text-align:center;color:#265531">本图由机器人“唐块儿”制作生成</p>
</article>
</body>
</html>
生成JS代码
往模板内填充数据主要用的就是document.getElementById('标签id名').innerHTML = '数据',如果数据是纯文字也可以使用document.getElementById('标签id名').textContent = '内容'
注意点1
但是innerHTML更加强大,里面的数据可以包含标签。比如我希望改变物价中的时间、价格这些字的颜色使得更加醒目,这时就可以使用
innerHTML = '20/11/03'来设置颜色,但如果是textContent这些标签将作为纯文字显示
注意点2
引号在相邻层分别使用不同引号区分,但如果是引号里的引号里的引号,还必须转义,比如
driver.execute_script("document.getElementById('id1').innerHTML = '<span style=\"color:#FF8200;\">20/11/03</span>';")
注入JS代码
注入代码就很简单了,创建headless浏览器对象,使用driver.execute_script()方法即可
网页截图
使用driver.find_element_by_tag_name()找到table元素,然后在location属性中提取出table的x,y坐标,width,height宽高,然后截取全屏,对截取的全屏图片根据x,y,width,height截图即可
table = driver.find_element_by_tag_name('table')
left = table.location['x'] - 10 # 左上右留白10个像素,下方留白30个像素
top = table.location['y'] - 10
right = left + table.size['width'] + 10
bottom = top + table.size['height'] + 30
# 截取全屏
driver.save_screenshot('capture.png')
driver.close()
driver.switch_to.window(handles[0])
# 截取部分并转成jpg格式
Image.open('capture.png').convert('RGB').crop((left, top, right, bottom)).save('capture.jpg')
完整代码
# -*- coding:utf-8 -*-
import time
import os
from selenium import webdriver
from selenium.webdriver import DesiredCapabilities
from selenium.webdriver.chrome.options import Options
from PIL import Image
outward_name = '青盒子'
price_json = eval("{1: [{'id': 53273, 'price': 900.0, 'region': '电信点卡', 'regionAlias': '电点', 'regionId': 1, 'server': '双梦', 'serverId': 1, 'saleCode': '出了', 'tradeTime': '20/12/16', 'outwardName': None, 'outwardId': 552, 'audit': 0, 'now': 0.0, 'exterior': '门派盒子', 'pricer': 'ʚ夜ɞ'}, {'id': 52939, 'price': 910.0, 'region': '电信点卡', 'regionAlias': '电点', 'regionId': 1, 'server': '华乾', 'serverId': 2, 'saleCode': '淘宝收了', 'tradeTime': '20/12/12', 'outwardName': None, 'outwardId': 552, 'audit': 0, 'now': 0.0, 'exterior': '门派盒子', 'pricer': '江江'}, {'id': 52386, 'price': 910.0, 'region': '电信点卡', 'regionAlias': '电点', 'regionId': 1, 'server': '双梦', 'serverId': 1, 'saleCode': '淘宝收了', 'tradeTime': '20/12/06', 'outwardName': None, 'outwardId': 552, 'audit': 0, 'now': 0.0, 'exterior': '门派盒子', 'pricer': '某些人比淘宝还贵'}, {'id': 51941, 'price': 915.0, 'region': '电信点卡', 'regionAlias': '电点', 'regionId': 1, 'server': '华乾', 'serverId': 2, 'saleCode': '收了', 'tradeTime': '20/12/01', 'outwardName': None, 'outwardId': 552, 'audit': 0, 'now': 0.0, 'exterior': '门派盒子', 'pricer': '林霄'}, {'id': 51859, 'price': 920.0, 'region': '电信点卡', 'regionAlias': '电点', 'regionId': 1, 'server': '姨妈', 'serverId': 4, 'saleCode': '淘宝收了', 'tradeTime': '20/11/29', 'outwardName': None, 'outwardId': 552, 'audit': 0, 'now': 0.0, 'exterior': '门派盒子', 'pricer': '不知名'}, {'id': 51703, 'price': 920.0, 'region': '电信点卡', 'regionAlias': '电点', 'regionId': 1, 'server': '唯满侠', 'serverId': 3, 'saleCode': '淘宝收了', 'tradeTime': '20/11/27', 'outwardName': None, 'outwardId': 552, 'audit': 0, 'now': 0.0, 'exterior': '门派盒子', 'pricer': '我膨胀了'}], 2: [{'id': 53058, 'price': 915.0, 'region': '双线一区', 'regionAlias': '双一', 'regionId': 2, 'server': '念破', 'serverId': 10, 'saleCode': '淘宝收了', 'tradeTime': '20/12/13', 'outwardName': None, 'outwardId': 552, 'audit': 0, 'now': 0.0, 'exterior': '门派盒子', 'pricer': 'Mmmmmmmolllllllllly'}, {'id': 52977, 'price': 915.0, 'region': '双线一区', 'regionAlias': '双一', 'regionId': 2, 'server': '纵月', 'serverId': 11, 'saleCode': '收了', 'tradeTime': '20/12/12', 'outwardName': None, 'outwardId': 552, 'audit': 0, 'now': 0.0, 'exterior': '门派盒子', 'pricer': '啊这'}, {'id': 51458, 'price': 880.0, 'region': '双线一区', 'regionAlias': '双一', 'regionId': 2, 'server': '念破', 'serverId': 10, 'saleCode': '出了', 'tradeTime': '20/11/23', 'outwardName': None, 'outwardId': 552, 'audit': 0, 'now': 0.0, 'exterior': '门派盒子', 'pricer': 'N'}, {'id': 49007, 'price': 910.0, 'region': '双线一区', 'regionAlias': '双一', 'regionId': 2, 'server': '念破', 'serverId': 10, 'saleCode': '收了', 'tradeTime': '20/10/18', 'outwardName': None, 'outwardId': 552, 'audit': 0, 'now': 0.0, 'exterior': '门派盒子', 'pricer': '茗?'}, {'id': 47682, 'price': 960.0, 'region': '双线一区', 'regionAlias': '双一', 'regionId': 2, 'server': '纵月', 'serverId': 11, 'saleCode': '淘宝收了', 'tradeTime': '20/10/08', 'outwardName': None, 'outwardId': 552, 'audit': 0, 'now': 0.0, 'exterior': '门派盒子', 'pricer': '戍北°'}, {'id': 46544, 'price': 900.0, 'region': '双线一区', 'regionAlias': '双一', 'regionId': 2, 'server': '纵月', 'serverId': 11, 'saleCode': '低价出了', 'tradeTime': '20/09/22', 'outwardName': None, 'outwardId': 552, 'audit': 0, 'now': 0.0, 'exterior': '门派盒子', 'pricer': '空格'}], 3: [{'id': 51133, 'price': 1000.0, 'region': '电信一区', 'regionAlias': '电一', 'regionId': 3, 'server': '龙虎', 'serverId': 18, 'saleCode': '低价出了', 'tradeTime': '20/11/18', 'outwardName': None, 'outwardId': 552, 'audit': 0, 'now': 0.0, 'exterior': '门派盒子', 'pricer': 'GUN'}, {'id': 50716, 'price': 950.0, 'region': '电信一区', 'regionAlias': '电一', 'regionId': 3, 'server': '蝶恋花', 'serverId': 19, 'saleCode': '出给牛', 'tradeTime': '20/11/11', 'outwardName': None, 'outwardId': 552, 'audit': 0, 'now': 0.0, 'exterior': '门派盒子', 'pricer': 'ym'}, {'id': 50696, 'price': 970.0, 'region': '电信一区', 'regionAlias': '电一', 'regionId': 3, 'server': '长安', 'serverId': 16, 'saleCode': '出给牛', 'tradeTime': '20/11/01', 'outwardName': None, 'outwardId': 552, 'audit': 0, 'now': 0.0, 'exterior': '门派盒子', 'pricer': '举个栗子'}, {'id': 44340, 'price': 930.0, 'region': '电信一区', 'regionAlias': '电一', 'regionId': 3, 'server': '蝶恋花', 'serverId': 19, 'saleCode': '出了', 'tradeTime': '20/08/30', 'outwardName': None, 'outwardId': 552, 'audit': 0, 'now': 0.0, 'exterior': '门派盒子', 'pricer': '南南南南栀?'}, {'id': 40141, 'price': 1020.0, 'region': '电信一区', 'regionAlias': '电一', 'regionId': 3, 'server': '长安', 'serverId': 16, 'saleCode': '收了', 'tradeTime': '20/06/29', 'outwardName': None, 'outwardId': 552, 'audit': 0, 'now': 0.0, 'exterior': '门派盒子', 'pricer': '??'}, {'id': 38468, 'price': 940.0, 'region': '电信一区', 'regionAlias': '电一', 'regionId': 3, 'server': '蝶恋花', 'serverId': 19, 'saleCode': '出了', 'tradeTime': '20/06/04', 'outwardName': None, 'outwardId': 552, 'audit': 0, 'now': 0.0, 'exterior': '门派盒子', 'pricer': '孑然若影'}], 4: [{'id': 50505, 'price': 920.0, 'region': '双线二区', 'regionAlias': '双二', 'regionId': 4, 'server': '飞龙在天', 'serverId': 23, 'saleCode': '出了', 'tradeTime': '20/11/08', 'outwardName': None, 'outwardId': 552, 'audit': 0, 'now': 0.0, 'exterior': '门派盒子', 'pricer': '一个废牛'}, {'id': 48045, 'price': 990.0, 'region': '双线二区', 'regionAlias': '双二', 'regionId': 4, 'server': '飞龙在天', 'serverId': 23, 'saleCode': '出了', 'tradeTime': '20/10/13', 'outwardName': None, 'outwardId': 552, 'audit': 0, 'now': 0.0, 'exterior': '门派盒子', 'pricer': '不靠谱 ?'}, {'id': 42464, 'price': 900.0, 'region': '双线二区', 'regionAlias': '双二', 'regionId': 4, 'server': '飞龙在天', 'serverId': 23, 'saleCode': '收了', 'tradeTime': '20/07/10', 'outwardName': None, 'outwardId': 552, 'audit': 0, 'now': 0.0, 'exterior': '门派盒子', 'pricer': '坏坏'}, {'id': 38942, 'price': 930.0, 'region': '双线二区', 'regionAlias': '双二', 'regionId': 4, 'server': '飞龙在天', 'serverId': 23, 'saleCode': '出给牛', 'tradeTime': '20/06/14', 'outwardName': None, 'outwardId': 552, 'audit': 0, 'now': 0.0, 'exterior': '门派盒子', 'pricer': '当归'}, {'id': 38782, 'price': 950.0, 'region': '双线二区', 'regionAlias': '双二', 'regionId': 4, 'server': '飞龙在天', 'serverId': 23, 'saleCode': '出了', 'tradeTime': '20/06/11', 'outwardName': None, 'outwardId': 552, 'audit': 0, 'now': 0.0, 'exterior': '门派盒子', 'pricer': 'Lynn°'}, {'id': 38695, 'price': 920.0, 'region': '双线二区', 'regionAlias': '双二', 'regionId': 4, 'server': '飞龙在天', 'serverId': 23, 'saleCode': '出了', 'tradeTime': '20/06/09', 'outwardName': None, 'outwardId': 552, 'audit': 0, 'now': 0.0, 'exterior': '门派盒子', 'pricer': 'Lynn°'}], 5: [{'id': 42006, 'price': 940.0, 'region': '双线四区', 'regionAlias': '双四', 'regionId': 5, 'server': '青梅煮酒', 'serverId': 15, 'saleCode': '淘宝收了', 'tradeTime': '20/07/28', 'outwardName': None, 'outwardId': 552, 'audit': 0, 'now': 0.0, 'exterior': '门派盒子', 'pricer': '玉阶生白露'}, {'id': 27003, 'price': 850.0, 'region': '双线四区', 'regionAlias': '双四', 'regionId': 5, 'server': '青梅煮酒', 'serverId': 15, 'saleCode': '出了', 'tradeTime': '20/03/16', 'outwardName': None, 'outwardId': 552, 'audit': 0, 'now': 0.0, 'exterior': '门派盒子', 'pricer': None}]}")
# 生成js代码
price_js = 'document.getElementById("price_title").innerHTML="{}•物价";'.format(outward_name)
for i in [1, 3, 2, 4, 5]:
msg = ''
for each_price in price_json[i][:10]:
sale_code = each_price['saleCode']
if '出' in each_price['saleCode']:
sale_code = '出'
elif '收' in each_price['saleCode']:
sale_code = '收'
sentence = '<span style=\'color:#FF8200;\'>{}</span> {} <span style=\'color:#008E97;\'>{}</span> {}</br>'.format(
each_price['tradeTime'],
each_price['server'],
each_price['price'],
sale_code)
msg += sentence
if len(price_json[i]) > 0:
price_js += ('document.getElementById("region_{}_data").innerHTML ="{}";'.format(i, msg))
# print(price_js)
# 浏览器参数
chrome_options = Options()
# chrome_options.add_argument('--headless')# 不显示界面,调试的时候显示方便观察
chrome_options.add_argument('--disable-gpu')
chrome_options.add_argument('--hide-scrollbars') # 隐藏滚动条
chrome_options.add_argument("start-maximized") # 最大化
path = 'C:/Program Files (x86)/Chromedriver/chromedriver.exe' # chromedriver地址
# 创建浏览器对象
driver = webdriver.Chrome(executable_path=path, options=chrome_options)
# 打开模板网页文件
driver.get('file:///{}/price.html'.format(os.getcwd())
# 延迟0.2s用于打开网页
time.sleep(0.2)
driver.execute_script(js_code) # 注入js
# 获取元素位置信息进行截图,并留白
table = driver.find_element_by_tag_name('table')
left = table.location['x'] - 10
top = table.location['y'] - 10
right = left + table.size['width'] + 10
bottom = top + table.size['height'] + 30
# 截取全屏
driver.save_screenshot('capture.png')
driver.close()
driver.switch_to.window(handles[0])
# 截取部分并转成jpg格式
Image.open('capture.png').convert('RGB').crop((left, top, right, bottom)).save('capture.jpg')