David Blume commited on 2019-03-22 23:48:17
Showing 1 changed files, with 78 additions and 101 deletions.
| ... | ... |
@@ -25,11 +25,12 @@ import json |
| 25 | 25 |
import xml |
| 26 | 26 |
import operator |
| 27 | 27 |
import cgi |
| 28 |
+import cStringIO |
|
| 28 | 29 |
import smtp_creds # Your own credentials, used in send_email() |
| 29 | 30 |
|
| 30 | 31 |
debug = True |
| 31 | 32 |
any_entry_added = False |
| 32 |
-tags_to_post = {'apple', 'google'}
|
|
| 33 |
+tags_to_post = {'apple', 'google', 'roku'}
|
|
| 33 | 34 |
authors_to_post = ['michael arrington',] |
| 34 | 35 |
|
| 35 | 36 |
# TODO 2018-01-18: Maybe combine fb_likes with bf_shares or something... |
| ... | ... |
@@ -38,9 +39,8 @@ rhs_metric_times = 'comment_times' |
| 38 | 39 |
|
| 39 | 40 |
localdir = '' |
| 40 | 41 |
|
| 41 |
-html_head = """ |
|
| 42 |
-<!DOCTYPE HTML PUBLIC '-//W3C//DTD HTML 4.01 Transitional//EN'> |
|
| 43 |
-<HTML><HEAD> |
|
| 42 |
+html_head = """<!DOCTYPE html> |
|
| 43 |
+<html><head> |
|
| 44 | 44 |
<title>TechCrunch Feed Filter</title> |
| 45 | 45 |
<!-- <link rel="alternate" type="application/rss+xml" title="RSS feed" href="http://techcrunch.dlma.com/rss_feed.xml" /> --> |
| 46 | 46 |
<link rel="alternate" type="application/rss+xml" title="RSS feed" href="http://feeds.feedburner.com/TrendingAtTechcrunch" /> |
| ... | ... |
@@ -57,12 +57,30 @@ html_head = """ |
| 57 | 57 |
tr.even { background:#%s; padding: 2em; }
|
| 58 | 58 |
tr.odd { background:#%s; padding-bottom: 2em; }
|
| 59 | 59 |
</style> |
| 60 |
-</HEAD> |
|
| 61 |
-<BODY> |
|
| 60 |
+ <script type="text/javascript" src="https://www.gstatic.com/charts/loader.js"></script> |
|
| 61 |
+ <script type="text/javascript"> |
|
| 62 |
+ google.charts.load('current', {'packages':['corechart']});
|
|
| 63 |
+ google.charts.setOnLoadCallback(drawChart); |
|
| 64 |
+ function drawChart() {
|
|
| 65 |
+ var options = {
|
|
| 66 |
+ width:300, |
|
| 67 |
+ height:68, |
|
| 68 |
+ pointSize:0.1, |
|
| 69 |
+ dataOpacity:1.0, |
|
| 70 |
+ series: { 0: {targetAxisIndex:0}, 1: {targetAxisIndex:1, color:'limegreen'} },
|
|
| 71 |
+ vAxis: { gridlines: {count: 0}, maxValue: 1 },
|
|
| 72 |
+ hAxis: { gridlines: {count: 0}, ticks: [] },
|
|
| 73 |
+ vAxes: { 0: {textStyle: {fontSize: 11, color: 'blue'} }, 1: {viewWindowMode: 'maximized', baselineColor: '#A0D0A0', textStyle: {fontSize: 11, color: 'limegreen'} } },
|
|
| 74 |
+ }; |
|
| 75 |
+%s |
|
| 76 |
+ } |
|
| 77 |
+ </script> |
|
| 78 |
+</head> |
|
| 79 |
+<body> |
|
| 62 | 80 |
<div align='center'><h3>TechCrunch Feed Filter</h3></div> |
| 63 | 81 |
This page shows what analysis is done to filter the noise away from the Techcrunch feed into |
| 64 | 82 |
<a href="http://feeds.feedburner.com/TrendingAtTechcrunch"> a more concise feed <img src="feed.png" alt="feed" height="14" width="14"></a>. |
| 65 |
-<a href="http://david.dlma.com/blog/my-techcrunch-feed-filter">Learn more about the Feed Filter</a>.<br /><br /> |
|
| 83 |
+<a href="https://david.dlma.com/blog/my-techcrunch-feed-filter">Learn more about the Feed Filter</a>.<br /><br /> |
|
| 66 | 84 |
""" |
| 67 | 85 |
|
| 68 | 86 |
html_footer = """ |
| ... | ... |
@@ -70,13 +88,21 @@ html_footer = """ |
| 70 | 88 |
</div><br /> |
| 71 | 89 |
<div align='center'>Thanks to <a href="http://www.feedparser.org/">The Universal Feed Parser module</a>, |
| 72 | 90 |
<a href="http://pyyaml.org/">PyYAML</a> and <a href="http://code.google.com/apis/chart/">Google Charts</a>.<br /> |
| 73 |
-<a href="http://git.dlma.com/techcrunch.git/">source</a> • <a href="techcrunch.yaml">raw data</a> • <a href="stats.txt">status</a><br />© 2011 <a href="http://david.dlma.com">David Blume</a></div><br /> |
|
| 74 |
-</BODY> |
|
| 75 |
-</HTML> |
|
| 91 |
+<a href="http://git.dlma.com/techcrunch.git/">source</a> • <a href="techcrunch.yaml">raw data</a> • <a href="stats.txt">status</a><br />© 2011 <a href="https://david.dlma.com">David Blume</a></div><br /> |
|
| 92 |
+</body> |
|
| 93 |
+</html> |
|
| 94 |
+""" |
|
| 95 |
+ |
|
| 96 |
+chart_data_header = """ var data = google.visualization.arrayToDataTable([ |
|
| 97 |
+ ['', 'Comments', 'Shares', {'type': 'string', 'role': 'style'}],
|
|
| 98 |
+""" |
|
| 99 |
+chart_data_middle = """ ]); |
|
| 100 |
+ var chart = new google.visualization.LineChart(document.getElementById('chart%d'));
|
|
| 101 |
+ options.backgroundColor = '#%s'; |
|
| 76 | 102 |
""" |
| 77 | 103 |
|
| 78 | 104 |
img_width = 300 |
| 79 |
-img_height = 50 |
|
| 105 |
+img_height = 68 |
|
| 80 | 106 |
|
| 81 | 107 |
series_1_color = "0000FF" |
| 82 | 108 |
series_2_color = "00AA00" |
| ... | ... |
@@ -118,10 +144,11 @@ def index_id(a_list, op, elem): |
| 118 | 144 |
return -1 |
| 119 | 145 |
|
| 120 | 146 |
|
| 121 |
-def make_chart_url(time_posted, lhs_times, lhs_values, rhs_times, |
|
| 122 |
- rhs_values, threshold_value, is_odd_row, tag_hit): |
|
| 147 |
+def write_chart_data(time_posted, lhs_times, lhs_values, rhs_times, |
|
| 148 |
+ rhs_values, threshold_value, image_index, tag_hit, chart_io): |
|
| 123 | 149 |
# lhs_times, lhs_values = zip(*comments) |
| 124 | 150 |
# rhs_times, rhs_values = zip(*rhs) |
| 151 |
+ is_odd_row = image_index % 2 |
|
| 125 | 152 |
|
| 126 | 153 |
if not len(lhs_times): |
| 127 | 154 |
lhs_times = [time_posted,] |
| ... | ... |
@@ -135,11 +162,6 @@ def make_chart_url(time_posted, lhs_times, lhs_values, rhs_times, |
| 135 | 162 |
lhs_times = [(i - time_posted) / 1800 for i in lhs_times] |
| 136 | 163 |
rhs_times = [(i - time_posted) / 1800 for i in rhs_times] |
| 137 | 164 |
|
| 138 |
- max_comment_time = max(lhs_times) |
|
| 139 |
- max_comment_value = max(lhs_values) |
|
| 140 |
- max_rhs_time = max(rhs_times) |
|
| 141 |
- max_rhs_value = max(rhs_values) |
|
| 142 |
- |
|
| 143 | 165 |
met_threshold_pt = -1 |
| 144 | 166 |
if threshold_value != -1: |
| 145 | 167 |
met_threshold_pt = index_id(rhs_values, operator.ge, threshold_value) |
| ... | ... |
@@ -151,54 +173,33 @@ def make_chart_url(time_posted, lhs_times, lhs_values, rhs_times, |
| 151 | 173 |
|
| 152 | 174 |
if is_odd_row != 0: |
| 153 | 175 |
bg_color = even_background |
| 154 |
- watermark_color = even_watermark |
|
| 155 | 176 |
else: |
| 156 | 177 |
bg_color = odd_background |
| 157 |
- watermark_color = odd_watermark |
|
| 158 | 178 |
|
| 159 |
- if len(lhs_values) < 8 and len(lhs_values) > 1: |
|
| 160 |
- # max_comment_value *= 2 |
|
| 161 |
- pass |
|
| 162 |
- elif len(lhs_values) == 1: |
|
| 163 |
- min_comment_value = 0 |
|
| 164 |
- if len(rhs_values) < 8 and len(rhs_values) > 1: |
|
| 165 |
- # max_rhs_value *= 2 |
|
| 166 |
- pass |
|
| 167 |
- elif len(rhs_values) == 1: |
|
| 168 |
- min_rhs_value = 0 |
|
| 169 |
- |
|
| 170 |
- min_comment_value = 0 |
|
| 171 |
- min_rhs_value = 0 |
|
| 172 |
- |
|
| 173 |
- chart_url = "http://chart.apis.google.com/chart?cht=lxy&chco=%s,%s&chs=%dx%d&chxs=0,%s|1,%s" % \ |
|
| 174 |
- (series_1_color, series_2_color, img_width, img_height, series_1_color, series_2_color) |
|
| 175 |
- chart_url += "&chd=t:%s|%s|%s|%s" % (','.join([str(n) for n in lhs_times]),
|
|
| 176 |
- ','.join([str(n) for n in lhs_values]), |
|
| 177 |
- ','.join([str(n) for n in rhs_times]), |
|
| 178 |
- ','.join([str(n) for n in rhs_values])) |
|
| 179 |
- # TODO: Consider watermark levels, like: |
|
| 180 |
- # chm=h,B0B0B0,1,0.3,1|r,E0E0E0,0,0,0.5 |
|
| 181 |
- if max_rhs_value > 0: |
|
| 182 |
- threshold_percent = max(0, min((float(threshold_value) / max_rhs_value) - 0.01, 1.0)) |
|
| 183 |
- else: |
|
| 184 |
- threshold_percent = 1.0 |
|
| 185 |
- chart_url += "&chm=r,%s,0,0,%1.3f" % (watermark_color, threshold_percent) |
|
| 186 |
- if met_threshold_pt != -1: |
|
| 179 |
+ chart_io.write(chart_data_header) |
|
| 180 |
+ for i in range(8): |
|
| 181 |
+ if i == met_threshold_pt: |
|
| 187 | 182 |
if tag_hit: |
| 188 |
- dot_color = tag_color |
|
| 189 |
- dot_shape = 'd' |
|
| 183 |
+ style = "'point { size: 5; fill-color: #FF0000; shape-type: diamond}'"
|
|
| 184 |
+ else: |
|
| 185 |
+ style = "'point { size: 5; fill-color: #FF8C00; }'"
|
|
| 186 |
+ else: |
|
| 187 |
+ style = "null" |
|
| 188 |
+ if i < len(lhs_values): |
|
| 189 |
+ lhs_value = str(lhs_values[i]) |
|
| 190 | 190 |
else: |
| 191 |
- dot_color = threshold_color |
|
| 192 |
- dot_shape = 'o' |
|
| 193 |
- chart_url += "|%s,%s,1,%d,10" % (dot_shape, dot_color, met_threshold_pt) |
|
| 194 |
- chart_url += "&chxt=y,r&chxl=0:|%d|%d|1:|%d|%d&chds=%d,%d,%d,%d,%d,%d,%d,%d" % \ |
|
| 195 |
- (min_comment_value, max_comment_value, min_rhs_value, max_rhs_value, |
|
| 196 |
- 0, max(7, max_comment_time), |
|
| 197 |
- min_comment_value, max_comment_value, |
|
| 198 |
- 0, max(7, max_rhs_time), |
|
| 199 |
- min_comment_value, max_rhs_value) |
|
| 200 |
- chart_url += "&chf=bg,s,%s&chdl=comments|shares" % (bg_color,) |
|
| 201 |
- return chart_url |
|
| 191 |
+ lhs_value = "null" |
|
| 192 |
+ if i < len(rhs_values): |
|
| 193 |
+ rhs_value = str(rhs_values[i]) |
|
| 194 |
+ else: |
|
| 195 |
+ rhs_value = "null" |
|
| 196 |
+ chart_io.write(" [%d, %s, %s, %s],\n" % (i, lhs_value, rhs_value, style))
|
|
| 197 |
+ chart_io.write(chart_data_middle % (image_index, bg_color)) |
|
| 198 |
+ if met_threshold_pt == -1 and not tag_hit: |
|
| 199 |
+ chart_io.write(" delete options.vAxes[1].baseline;\n")
|
|
| 200 |
+ else: |
|
| 201 |
+ chart_io.write(" options.vAxes[1].baseline = %d;\n" % (threshold_value,))
|
|
| 202 |
+ chart_io.write(" chart.draw(data, options);\n\n")
|
|
| 202 | 203 |
|
| 203 | 204 |
|
| 204 | 205 |
def process_feed(yaml_items): |
| ... | ... |
@@ -432,27 +433,6 @@ def Get_fb_stats(url_string): |
| 432 | 433 |
return shares, comments, likes |
| 433 | 434 |
|
| 434 | 435 |
|
| 435 |
-def save_image(url_string, file_path): |
|
| 436 |
- try: |
|
| 437 |
- f = urllib2.urlopen(url_string) |
|
| 438 |
- data = f.read() |
|
| 439 |
- f.close() |
|
| 440 |
- except (urllib2.URLError, httplib.BadStatusLine) as e: |
|
| 441 |
- if hasattr(e, 'reason'): # URLError |
|
| 442 |
- print "save_image: Error attempting to create", file_path[file_path.rfind('/')+1:], "Reason:", e.reason
|
|
| 443 |
- elif hasattr(e, 'code'): # URLError |
|
| 444 |
- print "save_image: Error attempting to create", file_path[file_path.rfind('/')+1:], "Code:", e.code
|
|
| 445 |
- else: |
|
| 446 |
- print "save_image: Error from urlopen", e |
|
| 447 |
- return url_string |
|
| 448 |
- |
|
| 449 |
- if len(data) > 50: |
|
| 450 |
- with open(file_path, 'wb') as f: |
|
| 451 |
- f.write(data) |
|
| 452 |
- return 'cache/' + os.path.basename(file_path) |
|
| 453 |
- return url_string |
|
| 454 |
- |
|
| 455 |
- |
|
| 456 | 436 |
def make_index_html(yaml_items, weekend_stats, weekday_stats): |
| 457 | 437 |
"""Writes a static index.html file from the YAML items.""" |
| 458 | 438 |
cur_time = int(time.time()) |
| ... | ... |
@@ -462,31 +442,33 @@ def make_index_html(yaml_items, weekend_stats, weekday_stats): |
| 462 | 442 |
|
| 463 | 443 |
files_to_delete = glob.glob(os.path.join(cache_path, '*.png')) |
| 464 | 444 |
|
| 465 |
- with codecs.open(new_index_fullpath, 'w', 'utf-8') as f: |
|
| 466 |
- f.write(html_head % (even_background, odd_background)) |
|
| 467 |
- |
|
| 468 |
- f.write('<div align="center">\n<table class="legend">\n<tr><th></th><th>Median</th><th>Mean</th><th>Std. Dev</th><th>Threshold</th></tr>\n')
|
|
| 469 |
- f.write('<tr><th>Weekday</th><td>%1.1f</td><td>%1.1f</td><td>%1.1f</td><td>%1.1f</td></tr>\n' % (weekday_stats[2][0], weekday_stats[2][1], weekday_stats[2][2], weekday_stats[2][1] + weekday_stats[2][2]))
|
|
| 470 |
- f.write('<tr><th>Weekend</th><td>%1.1f</td><td>%1.1f</td><td>%1.1f</td><td>%1.1f</td></tr>\n' % (weekend_stats[2][0], weekend_stats[2][1], weekend_stats[2][2], weekend_stats[2][1] + weekend_stats[2][2]))
|
|
| 471 |
- f.write('</table></div>\n<br />\n')
|
|
| 472 |
- |
|
| 473 |
- f.write('<div align="center">\n<table>\n')
|
|
| 445 |
+ chart_io = cStringIO.StringIO() |
|
| 474 | 446 |
for image_index, image in enumerate(yaml_items[:40]): |
| 475 | 447 |
tag_hit = False |
| 476 | 448 |
if image['author'].lower() in authors_to_post: |
| 477 | 449 |
tag_hit = True |
| 478 | 450 |
elif len(set([j.lower() for j in image['tags']]) & tags_to_post) > 0: |
| 479 | 451 |
tag_hit = True |
| 480 |
- chart_url = make_chart_url(image['orig_posted'], |
|
| 452 |
+ write_chart_data(image['orig_posted'], |
|
| 481 | 453 |
image['comment_times'], |
| 482 | 454 |
image['fb_comments'], |
| 483 | 455 |
image[rhs_metric_times], |
| 484 | 456 |
image[rhs_metric], |
| 485 | 457 |
image['qualified'], |
| 486 |
- image_index % 2, |
|
| 487 |
- tag_hit |
|
| 458 |
+ image_index, |
|
| 459 |
+ tag_hit, |
|
| 460 |
+ chart_io |
|
| 488 | 461 |
) |
| 489 |
- image_url = save_image(chart_url, os.path.join(cache_path, '%d_%d.png' % (cur_time, image_index))) |
|
| 462 |
+ |
|
| 463 |
+ with codecs.open(new_index_fullpath, 'w', 'utf-8') as f: |
|
| 464 |
+ f.write(html_head % (even_background, odd_background, chart_io.getvalue())) |
|
| 465 |
+ chart_io.close() |
|
| 466 |
+ f.write('<div align="center">\n<table class="legend">\n<tr><th></th><th>Median</th><th>Mean</th><th>Std. Dev</th><th>Threshold</th></tr>\n')
|
|
| 467 |
+ f.write('<tr><th>Weekday</th><td>%1.1f</td><td>%1.1f</td><td>%1.1f</td><td>%1.1f</td></tr>\n' % (weekday_stats[2][0], weekday_stats[2][1], weekday_stats[2][2], weekday_stats[2][1] + weekday_stats[2][2]))
|
|
| 468 |
+ f.write('<tr><th>Weekend</th><td>%1.1f</td><td>%1.1f</td><td>%1.1f</td><td>%1.1f</td></tr>\n' % (weekend_stats[2][0], weekend_stats[2][1], weekend_stats[2][2], weekend_stats[2][1] + weekend_stats[2][2]))
|
|
| 469 |
+ f.write('</table></div>\n<br />\n')
|
|
| 470 |
+ f.write('<div align="center">\n<table>\n')
|
|
| 471 |
+ for image_index, image in enumerate(yaml_items[:40]): |
|
| 490 | 472 |
f.write('<tr valign="center" class="%s">\n <td><strong><a href="%s">%s</a></strong> <span class="author">by %s</span></td>\n' % \
|
| 491 | 473 |
(image_index % 2 and "even" or "odd", |
| 492 | 474 |
image['link'], |
| ... | ... |
@@ -495,12 +477,7 @@ def make_index_html(yaml_items, weekend_stats, weekday_stats): |
| 495 | 477 |
) |
| 496 | 478 |
) |
| 497 | 479 |
f.write(' <td>%s<td>\n' % (image['qualified'] != -1 and '<img src="star_30.png" width="30" height="29" />' or ''))
|
| 498 |
- f.write(' <td><img src="%s" width="%d" height="%d" border="0" /></td></tr>\n' % \
|
|
| 499 |
- (image_url, |
|
| 500 |
- img_width, |
|
| 501 |
- img_height |
|
| 502 |
- ) |
|
| 503 |
- ) |
|
| 480 |
+ f.write(' <td><div id="chart%d" /></td></tr>\n' % (image_index, ))
|
|
| 504 | 481 |
f.write(html_footer) |
| 505 | 482 |
|
| 506 | 483 |
if os.path.exists(index_fullpath): |
| 507 | 484 |