Detect Duplicate Questions Detect Duplicate Questions

Once upon a time, there was a golfing site. It had a problem: people would post similar or identical questions again and again. You have been chosen selected forced conscripted blackmailed requested to automate the process of deciding whether a question is a duplicate of an existing one, by whatever means necessary (see Rules).

Input

Your program must accept a single URL as input. It may assume that this leads to a question on codegolf.stackexchange.com.

Output

Search the site for similar questions. If you think that the input question is a duplicate of an existing question (or vice versa), output the other question's URL. You may output multiple URLs, separated by new lines. At the end of your output, output end (on a separate line).

Scoring


If a question that you output was indeed marked as a duplicate of the input question (or vice versa), you score 4 points. This is a "correct guess". 
For each false positive (aka "incorrect guess"), you lose 2 points.
For each question that was actually a duplicate but does not appear in your output (aka "missing guess"), lose 1 point.


The highest score after handling 32 input questions wins. These 32 questions are a "round". At the beginning of each round, the scores will be reset to 0. One round will be run every few days, and the leaderboard updated after each round.

Rules


If questions A and C are both closed as duplicates of B, A will count as a duplicate of C and vice versa.
At the start of each round, your program may not possess any data about any questions (i.e. no hardcoding), except on how to parse the website.
However, you may keep data in external files during a round.
No data may be kept between rounds.
Your output must have a trailing new line.
You may not use any data from the website except search results and the URL, title, tags and text of a question, with or without formatting. For example, you may not use the text "marked as duplicate by foo, bar..." that appears on duplicate questions.
You may retrieve this data directly from the site, via data.SE or via the API.
Each submission must have a name.
Each submission must have clear version numbering.
If a submission does not produce output after a time limit (to be decided; please state how long your submission takes) it will be killed and lose 8 points.
Python 3
I am giving this entry the name The Differ.
Code:
import urllib.request, gzip, re, json, difflib, sys
API_URL = &quot;https://api.stackexchange.com/&quot;
qurl = input()
qid = int(re.search(&quot;\d+&quot;,qurl).group(0))
def request(url,wrapper=False,**params):
    params.setdefault(&quot;filter&quot;,&quot;withbody&quot;)
    params.setdefault(&quot;site&quot;,&quot;codegolf&quot;)
    url = API_URL + url + &quot;?&quot;+&quot;&amp;&quot;.join([str(k)+&quot;=&quot;+str(v) for k,v in params.items()])
    compressed_response = urllib.request.urlopen(url)
    response = gzip.decompress(compressed_response.read()).decode(&quot;utf8&quot;)
    response_object = json.loads(response)
    if wrapper:
        return response_object
    else:
        return response_object[&quot;items&quot;]
question = request(&quot;questions/%s&quot;%qurl)[0]
tags = &quot;;&quot;.join(question[&quot;tags&quot;])
title = question[&quot;title&quot;]
escaped = title.replace(&quot; &quot;,&quot;%20&quot;)
related = request(&quot;similar&quot;,title=escaped,pagesize=100)
hasmore = False
length = sys.maxsize
for tag in question[&quot;tags&quot;]:
    result = request(&quot;search&quot;,tagged=tag,
                     wrapper=True,
                     filter=&quot;!-*f(6rc.cI8O&quot;,
                     pagesize=100)
    if result[&quot;total&quot;] &lt; length:
        length = result[&quot;total&quot;]
        related.extend(result[&quot;items&quot;])
        hasmore = result[&quot;has_more&quot;]
        besttag = tag
related.extend(best)
if length &lt; 1500:
    for page in itertools.count(2):
        if not hasmore:
            break
        response = request(&quot;search&quot;,
                           tagged=besttag,
                           page=page,
                           pagesize=100,
                           filter=&quot;!-*f(6rc.cI8O&quot;,
                           wrapper=True)
        hasmore = response[&quot;has_more&quot;]
        related.extend(result[&quot;items&quot;])
matcher = difflib.SequenceMatcher(None, question[&quot;body&quot;], None)
titlematcher = difflib.SequenceMatcher(None, question[&quot;title&quot;], None)
seen = set()
seen.add(question[&quot;question_id&quot;])
for possible in related:
    matcher.set_seq2(possible[&quot;body&quot;])
    titlematcher.set_seq2(possible[&quot;title&quot;])
    score = matcher.ratio()+titlematcher.ratio()
    qid = possible[&quot;question_id&quot;]
    if score &gt; .85 and qid not in seen:
        print(qid)
        seen.add(qid)
print(&quot;end&quot;)

The filter &quot;!-*f(6rc.cI8O&quot; included the total parameter on the global wrapper object and the body parameter on questions.
This entry makes two API requests plus one per tag on the question plus one per hundred questions in its least used tag. If it hits an api throttle (which it doesn't check for), it will raise an urllib.error.HTTPError: HTTP Error 400: Bad Request