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 |