实现步骤

  1. 编写模板网页
  2. 生成JS代码
  3. 注入JS代码
  4. 网页截图

在这里我以唐块儿机器人的物价图片为例,效果图如下
avatar

Step1.编写模板网页

在这里我喜欢Markdown的样式,GitHub地址:https://github.com/sindresorhus/github-markdown-css,下载之后在HTML模板文件中导入样式,把需要Markdown样式的标签放到<article></article>标签内,即可让原来的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>

Step2.生成JS代码

往模板内填充数据主要用的就是document.getElementById('标签id名').innerHTML = '数据',如果数据是纯文字也可以使用document.getElementById('标签id名').textContent = '内容'

注意点1

但是innerHTML更加强大,里面的数据可以包含标签。比如我希望改变物价中的时间、价格这些字的颜色使得更加醒目,这时就可以使用

innerHTML = '<span style='color:#FF8200;'>20/11/03</span>'来设置颜色,但如果是textContent这些标签将作为纯文字显示

注意点2

引号在相邻层分别使用不同引号区分,但如果是引号里的引号里的引号,还必须转义,比如

driver.execute_script("document.getElementById('id1').innerHTML = '<span style=\"color:#FF8200;\">20/11/03</span>';")

Step3.注入JS代码

注入代码就很简单了,创建headless浏览器对象,使用driver.execute_script()方法即可

Step4.网页截图

使用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')