This post originated from an RSS feed registered with Python Buzz
by Thomas Guest.
Original Post: Spam, Typo, Subversion Logs
Feed Title: Word Aligned: Category Python
Feed URL: http://feeds.feedburner.com/WordAlignedCategoryPython
Feed Description: Dynamic languages in general. Python in particular. The adventures of a space sensitive programmer.
This blog got hit by some spam promoting online gambling sites,
even though I’d cranked up most of the built in anti-spam settings. I
went across to the Typo website looking for advice and discovered:
The Typo Trac is currently offline because of a high level of
spam. It shall return as soon as we’ve got some more protection
added to it.
Unfunnily enough, I experimented with an open Trac project a while
back and it too received spam contributions cunningly hidden
where a regular reader wouldn’t notice.
Examining the Typo Subversion Logs
Fortunately the Typo Subversion server remains up and running. I
took a look at the log files to see if there were any spam-related
improvements since I’d originally installed Typo and the latest release
(4.0.3 at the time of writing). There were a few hits.
$ svn log -r1133:1231 svn://typosphere.org/typo | grep spam
Add spam setting for Akismet key. I still need to write the Akismet glue code, but it won't work without a key.
Big spam filtering upgrade.
Comments (and trackbacks) that fail the spam check are marked as unpublished
registering of spam/ham classification with akismet). JustPresumedHam
of articles whose classification you have confirmed as well as a simple spam/ham
Use published_at for comment spam checks. Closes #1089
A Closer Examination
This superficial inspection suggests that, out of 99 changes, 4 relate
to spam – suggesting that the Typo developers spent less than 5%
of their effort making anti-spam changes in the period concerned.
A more useful statistic would be the number of files which were
modified for anti-spam purposes. It’s rather harder to extract this
number using simple shell programs such as grep so I wrote a
Python program to analyse the the svn log output. I used the
--xml option to the svn log command to provide me with more
structured output, and the Python minidom XML module proved more than
up to the task of parsing this output.
Here’s what this program told me.
$ svn log -r1133:1231 svn://typosphere.org/typo --xml --verbose | \
process_svn_log.py spam akismet
Found /spam|akismet/i in 9/99 changes affecting 72/270 files.
Note that I included Akismet in my pattern match. As I understand
it, Akismet is a service specifically designed to protect blogs
against spam.
I could dig even deeper and find out how many lines of code were
changed, but I don’t think it’s worth it. This is a pretty blunt tool,
but it does tell us that some smart programmers are having to spend
almost as much time fighting dumb spammers as they are writing more
useful code.
The svn log processor
For the record, here’s my program. It’s best suited to the job it
actually did but it’s simple enough that I’ll be able to adapt it for
use elsewhere.
process_svn_log.py
""" This program filters 'svn log --xml --verbose' output
for log entries which match patterns.
This output has the form:
<?xml version="1.0"?>
<log>
<logentry
revision="1133">
<author>scott</author>
<date>2006-07-13T17:26:26.186291Z</date>
<paths>
<path
action="M">/trunk/app/views/admin/feedback/list.rhtml</path>
</paths>
<msg>Make search+pagination work right</msg>
</logentry>
</log>
"""defusage(program):print"""\
Usage: %s PATTERN ...
Searches the output from 'svn log --xml --verbose' for log entries whose
message matches the supplied PATTERN(s) and yields summary statistics.
Example:
svn log -r1133:1231 svn://typosphere.org/typo --xml --verbose | %s spam"""%(program,program)defelements(node,tagname):" Return named child elements of a DOM node. "returnnode.getElementsByTagName(tagname)defcount_paths(logentries):" Count repository path changes logged. "returnsum(1forlogentryinlogentriesforpathsinelements(logentry,"paths")forpathinelements(paths,"path"))deflog_msg_matches(logentry,matcher):" Return true if the logentry message matches, false otherwise. "msgs=elements(logentry,"msg")assertlen(msgs)==1,"Require a single log message per log entry."returnmatcher(msgs[0].childNodes[0].data)isnotNonedefprocess(log,patterns):" Process the input svn log, looking for messages matching the input patterns. "importrepattern="|".join(patterns)matcher=re.compile(pattern,re.IGNORECASE).searchentries=elements(log,"logentry")matches=[entryforentryinentriesiflog_msg_matches(entry,matcher)]paths=count_paths(entries)matching_paths=count_paths(matches)print"Found /%s/i in %d/%d changes affecting %d/%d files."%(pattern,len(matches),len(entries),matching_paths,paths)defmain(argv):iflen(argv)==1:usage(argv[0])else:fromxml.dom.minidomimportparseprocess(parse(sys.stdin),argv[1:])if__name__=="__main__":importsysmain(sys.argv)