Skip to content

Instantly share code, notes, and snippets.

@tai-fukaya
Last active June 3, 2021 12:49
Show Gist options
  • Save tai-fukaya/874c08bde4f2f24fd34f62d55f6e5b24 to your computer and use it in GitHub Desktop.
Save tai-fukaya/874c08bde4f2f24fd34f62d55f6e5b24 to your computer and use it in GitHub Desktop.
lighthouseの結果を自動で取得する(雑)
import argparse
import csv
import datetime
import json
import os
import re
import subprocess
import time
# python execute-lighthouse-test.py target(file, url) --output_dir=<path> --count=<count>
parser = argparse.ArgumentParser()
parser.add_argument("target")
parser.add_argument("--output_dir", type=str, default=None)
parser.add_argument("--count", type=int, default=5)
args = parser.parse_args()
count = args.count
output_dir = args.output_dir
SETTINGS = [
{'id': 'RequestedUrl', 'query': '.requestedUrl', 'default': ''},
{'id': 'performance', 'query': '.categories.performance.score', 'default': 0},
{'id': 'accessibility', 'query': '.categories.accessibility.score', 'default': 0},
{'id': 'bestPractices', 'query': '.categories.best-practices.score', 'default': 0},
{'id': 'seo', 'query': '.categories.seo.score', 'default': 0},
{'id': 'pwa', 'query': '.categories.pwa.score', 'default': 0},
# performance
{'id': 'FirstContentfulPaint', 'query': '.audits.first-contentful-paint.numericValue', 'default': 0},
{'id': 'FirstMeaningfulPaint', 'query': '.audits.first-meaningful-paint.numericValue', 'default': 0},
{'id': 'LargestContentfulPaint', 'query': '.audits.largest-contentful-paint.numericValue', 'default': 0},
{'id': 'Interactive', 'query': '.audits.interactive.numericValue', 'default': 0},
{'id': 'SpeedIndex', 'query': '.audits.speed-index.numericValue', 'default': 0},
{'id': 'TotalBlockingTime', 'query': '.audits.total-blocking-time.numericValue', 'default': 0},
{'id': 'MaxPotentialFID', 'query': '.audits.max-potential-fid.numericValue', 'default': 0},
{'id': 'CumulativeLayoutShift', 'query': '.audits.cumulative-layout-shift.numericValue', 'default': 0},
# performance details
{'id': 'CriticalRequestChains', 'query': '.audits.critical-request-chains.displayValue', 'default': ''}, # FCP, LCP
{'id': 'LongTasks', 'query': '.audits.long-tasks.displayValue', 'default': ''}, # TBT
{'id': 'LayoutShiftElements', 'query': '.audits.layout-shift-elements.displayValue', 'default': ''}, # CLS
{'id': 'ServerResponseTime', 'query': '.audits.server-response-time.numericValue', 'default': 0}, # FCP, LCP
{'id': 'UnusedCssRules', 'query': '.audits.unused-css-rules.displayValue', 'default': ''}, # FCP, LCP
{'id': 'UnusedJavascript', 'query': '.audits.unused-javascript.displayValue', 'default': ''}, # LCP
{'id': 'TotalByteWeight', 'query': '.audits.total-byte-weight.numericValue', 'default': 0}, # LCP
{'id': 'BootupTimeScore', 'query': '.audits.bootup-time.score', 'default': 0}, # TBT
{'id': 'BootupTime', 'query': '.audits.bootup-time.numericValue', 'default': 0}, # TBT
{'id': 'MainthreadWorkBreakdownScore', 'query': '.audits.mainthread-work-breakdown.score', 'default': 0}, # TBT
{'id': 'MainthreadWorkBreakdown', 'query': '.audits.mainthread-work-breakdown.numericValue', 'default': 0}, # TBT
{'id': 'DomSizeScore', 'query': '.audits.dom-size.score', 'default': 0}, # TBT
{'id': 'DomSize', 'query': '.audits.dom-size.numericValue', 'default': 0}, # TBT
{'id': 'ThirdPartyFacades', 'query': '.audits.third-party-facades.displayValue', 'default': ''}, # TBT
{'id': 'ThirdPartySummary', 'query': '.audits.third-party-summary.score', 'default': 0}, # TBT score
{'id': 'UnsizedImages', 'query': '.audits.unsized-images.score', 'default': 0}, # CLS score
{'id': 'FontDisplay', 'query': '.audits.font-display.score', 'default': 0}, # FCP, LCP score
{'id': 'UsesTextCompression', 'query': '.audits.uses-text-compression.score', 'default': 0} # FCP, LCP score
# {'id': 'RenderBlockingResources', 'query': '.audits.render-blocking-resources.score', 'default': 0}, # FCP, LCP score
# {'id': 'Redirects', 'query': '.audits.redirects.score', 'default': 0}, # FCP, LCP score
# {'id': 'UsesRelPreconnect', 'query': '.audits.uses-rel-preconnect.score', 'default': 0}, # FCP, LCP score
# {'id': 'UsesRelPreload', 'query': '.audits.uses-rel-preload.score', 'default': 0}, # FCP, LCP score
# {'id': 'UnminifiedJavascript', 'query': '.audits.unminified-javascript.score', 'default': 0}, # FCP, LCP score
# {'id': 'UnminifiedCss', 'query': '.audits.unminified-css.score', 'default': 0}, # FCP, LCP score
# {'id': 'LargestContentfulPaintElement', 'query': '.audits.largest-contentful-paint-element.displayValue', 'default': ''}, # LCP
# {'id': 'PreloadLcpImage', 'query': '.audits.preload-lcp-image.score', 'default': 0}, # LCP score
# {'id': 'EfficientAnimatedContent', 'query': '.audits.efficient-animated-content.score', 'default': 0}, # LCP score
# {'id': 'DuplicatedJavascript', 'query': '.audits.duplicated-javascript.score', 'default': 0}, # TBT score
# {'id': 'LegacyJavascript', 'query': '.audits.legacy-javascript.score', 'default': 0} # TBT score
]
results = []
def makedirs(path):
if not os.path.isdir(path):
os.makedirs(path)
def exec_lighthouse(url):
# --preset=desktop
commands = ['lighthouse', url, '--quiet', '--chrome-flags="--headless"', '--output=json']
return subprocess.check_output(commands)
def save_result(path, output):
with open(path, 'w') as f:
f.write(output)
def get_json_value(obj, query_arr, default):
o = obj.get(query_arr[0])
if len(query_arr) > 1:
return get_json_value(o or {}, query_arr[1:], default)
else:
return o or default
def parse(output):
parsed = json.loads(output)
result = {}
for s in SETTINGS:
# TODO Array[]
query_arr = s.get('query', '.').split('.')[1:]
value = get_json_value(parsed, query_arr, s.get('default'))
if type(value) is int or type(value) is float:
value = str(value)
elif type(value) is unicode:
value = value.encode('utf_8')
result[s.get('id')] = value
results.append(result)
return result
if output_dir is not None:
makedirs(output_dir)
# read target url
lines = []
if args.target.find("://") >= 0:
lines.append(args.target)
else:
with open(args.target, 'r') as f:
lines = f.read().split("\n")
print(','.join([ x.get('id') for x in SETTINGS if 'id' in x]))
for url in lines:
for i in range(count):
try:
output = exec_lighthouse(url)
if output_dir is not None:
now = datetime.datetime.now().strftime('%Y%m%d%H%M%S')
key = re.sub('[^a-zA-Z0-9]+', '-', url) + '_' + now
output_file_path = output_dir + '/' + key + '.json'
save_result(output_file_path, output)
parsed = parse(output)
print(','.join([parsed.get(x.get('id')) for x in SETTINGS if 'id' in x]))
except subprocess.CalledProcessError as e:
pass
time.sleep(.1)
if output_dir is not None:
output_result_path = output_dir + '/' + datetime.datetime.now().strftime('%Y%m%d%H%M%S') + '.tsv'
with open(output_result_path, 'w') as f:
writer = csv.DictWriter(f, [ x.get('id') for x in SETTINGS if 'id' in x], delimiter='\t')
writer.writeheader()
writer.writerows(results)
We can make this file beautiful and searchable if this error is corrected: No commas found in this CSV file in line 0.
http://example.com/
http://example.com/
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment