PageRenderTime 31ms CodeModel.GetById 1ms RepoModel.GetById 0ms app.codeStats 0ms

/duckduckgo.py

http://github.com/mikejs/python-duckduckgo
Python | 164 lines | 154 code | 6 blank | 4 comment | 6 complexity | cec97ced82975939870fb3e454723bb4 MD5 | raw file
Possible License(s): BSD-3-Clause
  1. #!/usr/bin/env python
  2. import urllib
  3. import urllib2
  4. from xml.etree import ElementTree
  5. __version__ = 0.1
  6. def query(query, useragent='python-duckduckgo 0.1'):
  7. """
  8. Query Duck Duck Go, returning a Results object.
  9. Here's a query that's unlikely to change:
  10. >>> result = query('1 + 1')
  11. >>> result.type
  12. 'nothing'
  13. >>> result.answer.text
  14. '1 + 1 = 2'
  15. >>> result.answer.type
  16. 'calc'
  17. """
  18. params = urllib.urlencode({'q': query, 'o': 'x'})
  19. url = 'http://duckduckgo.com/?' + params
  20. request = urllib2.Request(url, headers={'User-Agent': useragent})
  21. response = urllib2.urlopen(request)
  22. xml = ElementTree.fromstring(response.read())
  23. response.close()
  24. return Results(xml)
  25. class Results(object):
  26. def __init__(self, xml):
  27. self.type = {'A': 'answer', 'D': 'disambiguation',
  28. 'C': 'category', 'N': 'name',
  29. 'E': 'exclusive', '': 'nothing'}[xml.findtext('Type', '')]
  30. self.api_version = xml.attrib.get('version', None)
  31. self.heading = xml.findtext('Heading', '')
  32. self.results = [Result(elem) for elem in xml.getiterator('Result')]
  33. self.related = [Result(elem) for elem in
  34. xml.getiterator('RelatedTopic')]
  35. self.abstract = Abstract(xml)
  36. answer_xml = xml.find('Answer')
  37. if answer_xml is not None:
  38. self.answer = Answer(answer_xml)
  39. if not self.answer.text:
  40. self.answer = None
  41. else:
  42. self.answer = None
  43. image_xml = xml.find('Image')
  44. if image_xml is not None and image_xml.text:
  45. self.image = Image(image_xml)
  46. else:
  47. self.image = None
  48. class Abstract(object):
  49. def __init__(self, xml):
  50. self.html = xml.findtext('Abstract', '')
  51. self.text = xml.findtext('AbstractText', '')
  52. self.url = xml.findtext('AbstractURL', '')
  53. self.source = xml.findtext('AbstractSource')
  54. class Result(object):
  55. def __init__(self, xml):
  56. self.html = xml.text
  57. self.text = xml.findtext('Text')
  58. self.url = xml.findtext('FirstURL')
  59. icon_xml = xml.find('Icon')
  60. if icon_xml is not None:
  61. self.icon = Image(icon_xml)
  62. else:
  63. self.icon = None
  64. class Image(object):
  65. def __init__(self, xml):
  66. self.url = xml.text
  67. self.height = xml.attrib.get('height', None)
  68. self.width = xml.attrib.get('width', None)
  69. class Answer(object):
  70. def __init__(self, xml):
  71. self.text = xml.text
  72. self.type = xml.attrib.get('type', '')
  73. def main():
  74. import sys
  75. from optparse import OptionParser
  76. parser = OptionParser(usage="usage: %prog [options] query",
  77. version="ddg %s" % __version__)
  78. parser.add_option("-o", "--open", dest="open", action="store_true",
  79. help="open results in a browser")
  80. parser.add_option("-n", dest="n", type="int", default=3,
  81. help="number of results to show")
  82. parser.add_option("-d", dest="d", type="int", default=None,
  83. help="disambiguation choice")
  84. (options, args) = parser.parse_args()
  85. q = ' '.join(args)
  86. if options.open:
  87. import urllib
  88. import webbrowser
  89. webbrowser.open("http://duckduckgo.com/?%s" % urllib.urlencode(
  90. dict(q=q)), new=2)
  91. sys.exit(0)
  92. results = query(q)
  93. if options.d and results.type == 'disambiguation':
  94. try:
  95. related = results.related[options.d - 1]
  96. except IndexError:
  97. print "Invalid disambiguation number."
  98. sys.exit(1)
  99. results = query(related.url.split("/")[-1].replace("_", " "))
  100. if results.answer and results.answer.text:
  101. print "Answer: %s\n" % results.answer.text
  102. elif results.abstract and results.abstract.text:
  103. print "%s\n" % results.abstract.text
  104. if results.type == 'disambiguation':
  105. print ("'%s' can mean multiple things. You can re-run your query "
  106. "and add '-d #' where '#' is the topic number you're "
  107. "interested in.\n" % q)
  108. for i, related in enumerate(results.related[0:options.n]):
  109. name = related.url.split("/")[-1].replace("_", " ")
  110. summary = related.text
  111. if len(summary) < len(related.text):
  112. summary += "..."
  113. print '%d. %s: %s\n' % (i + 1, name, summary)
  114. else:
  115. for i, result in enumerate(results.results[0:options.n]):
  116. summary = result.text[0:70].replace("&nbsp;", " ")
  117. if len(summary) < len(result.text):
  118. summary += "..."
  119. print "%d. %s" % (i + 1, summary)
  120. print " <%s>\n" % result.url
  121. if __name__ == '__main__':
  122. main()