├── LICENSE ├── MANIFEST ├── README.rst ├── duckduckgo.py └── setup.py /LICENSE: -------------------------------------------------------------------------------- 1 | BSD-style license 2 | ================= 3 | 4 | Copyright (c) 2010 Michael Stephens 5 | Copyright (c) 2012-2013 Michael Smith 6 | 7 | All rights reserved. 8 | 9 | Redistribution and use in source and binary forms, with or without 10 | modification, are permitted provided that the following conditions are met: 11 | 12 | * Redistributions of source code must retain the above copyright notice, 13 | this list of conditions and the following disclaimer. 14 | * Redistributions in binary form must reproduce the above copyright notice, 15 | this list of conditions and the following disclaimer in the documentation 16 | and/or other materials provided with the distribution. 17 | * Neither the name of the author nor the names of its contributors may be 18 | used to endorse or promote products derived from this software without 19 | specific prior written permission. 20 | 21 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 22 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 23 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 24 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR 25 | CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 26 | EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 27 | PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 28 | PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 29 | LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 30 | NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 31 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 32 | -------------------------------------------------------------------------------- /MANIFEST: -------------------------------------------------------------------------------- 1 | LICENSE 2 | README.rst 3 | duckduckgo.py 4 | setup.py 5 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | ================== 2 | python-duckduckgo 3 | ================== 4 | 5 | A Python library for querying the DuckDuckGo API. 6 | 7 | Copyright (c) 2010 Michael Stephens 8 | Copyright (c) 2012-2013 Michael Smith 9 | 10 | Released under a 3-clause BSD license, see LICENSE for details. 11 | 12 | Latest Source: http://github.com/crazedpsyc/python-duckduckgo 13 | Original source: http://github.com/mikejs/python-duckduckgo (outdated) 14 | 15 | This version has been forked from the original to handle some new features of 16 | the API, and switch from XML to JSON. 17 | 18 | Installation 19 | ============ 20 | 21 | To install run 22 | 23 | ``python setup.py install`` 24 | 25 | Usage 26 | ===== 27 | 28 | >>> import duckduckgo 29 | >>> r = duckduckgo.query('DuckDuckGo') 30 | >>> r.type 31 | u'answer' 32 | >>> r.results[0].text 33 | u'Official site' 34 | >>> r.results[0].url 35 | u'http://duckduckgo.com/' 36 | >>> r.abstract.url 37 | u'http://en.wikipedia.org/wiki/Duck_Duck_Go' 38 | >>> r.abstract.source 39 | u'Wikipedia' 40 | 41 | >>> r = duckduckgo.query('Python') 42 | >>> r.type 43 | u'disambiguation' 44 | >>> r.related[1].text 45 | u'Python (programming language), a computer programming language' 46 | >>> r.related[1].url 47 | u'http://duckduckgo.com/Python_(programming_language)' 48 | >>> r.related[7].topics[0].text # weird, but this is how the DDG API is currently organized 49 | u'Armstrong Siddeley Python, an early turboprop engine' 50 | 51 | 52 | >>> r = duckduckgo.query('1 + 1') 53 | >>> r.type 54 | u'nothing' 55 | >>> r.answer.text 56 | u'1 + 1 = 2' 57 | >>> r.answer.type 58 | u'calc' 59 | 60 | >>> print duckduckgo.query('19301', kad='es_ES').answer.text 61 | 19301 es un código postal de Paoli, PA 62 | >>> print duckduckgo.query('how to spell test', html=True).answer.text 63 | Test appears to be spelled right!
Suggestions: test, testy, teat, tests, rest, yest. 64 | 65 | The easiest method of quickly grabbing the best (hopefully) API result is to use duckduckgo.get_zci:: 66 | >>> print duckduckgo.get_zci('foo') 67 | The terms foobar /ˈfʊːbɑːr/, fubar, or foo, bar, baz and qux are sometimes used as placeholder names in computer programming or computer-related documentation. (https://en.wikipedia.org/wiki/Foobar) 68 | >>> print ddg.get_zci('foo fighters site') 69 | http://www.foofighters.com/us/home 70 | 71 | Special keyword args for query(): 72 | - useragent - string, The useragent used to make API calls. This is somewhat irrelevant, as they are not logged or used on DuckDuckGo, but it is retained for backwards compatibility. 73 | - safesearch - boolean, enable or disable safesearch. 74 | - html - boolean, Allow HTML in responses? 75 | 76 | -------------------------------------------------------------------------------- /duckduckgo.py: -------------------------------------------------------------------------------- 1 | # duckduckgo.py - Library for querying the DuckDuckGo API 2 | # 3 | # Copyright (c) 2010 Michael Stephens 4 | # Copyright (c) 2012-2013 Michael Smith 5 | # 6 | # See LICENSE for terms of usage, modification and redistribution. 7 | 8 | import urllib 9 | import urllib2 10 | import json as j 11 | import sys 12 | 13 | __version__ = 0.242 14 | 15 | 16 | def query(query, useragent='python-duckduckgo '+str(__version__), safesearch=True, html=False, meanings=True, **kwargs): 17 | """ 18 | Query DuckDuckGo, returning a Results object. 19 | 20 | Here's a query that's unlikely to change: 21 | 22 | >>> result = query('1 + 1') 23 | >>> result.type 24 | 'nothing' 25 | >>> result.answer.text 26 | '1 + 1 = 2' 27 | >>> result.answer.type 28 | 'calc' 29 | 30 | Keword arguments: 31 | useragent: UserAgent to use while querying. Default: "python-duckduckgo %d" (str) 32 | safesearch: True for on, False for off. Default: True (bool) 33 | html: True to allow HTML in output. Default: False (bool) 34 | meanings: True to include disambiguations in results (bool) 35 | Any other keyword arguments are passed directly to DuckDuckGo as URL params. 36 | """ % __version__ 37 | 38 | safesearch = '1' if safesearch else '-1' 39 | html = '0' if html else '1' 40 | meanings = '0' if meanings else '1' 41 | params = { 42 | 'q': query, 43 | 'o': 'json', 44 | 'kp': safesearch, 45 | 'no_redirect': '1', 46 | 'no_html': html, 47 | 'd': meanings, 48 | } 49 | params.update(kwargs) 50 | encparams = urllib.urlencode(params) 51 | url = 'http://api.duckduckgo.com/?' + encparams 52 | 53 | request = urllib2.Request(url, headers={'User-Agent': useragent}) 54 | response = urllib2.urlopen(request) 55 | json = j.loads(response.read()) 56 | response.close() 57 | 58 | return Results(json) 59 | 60 | 61 | class Results(object): 62 | 63 | def __init__(self, json): 64 | self.type = {'A': 'answer', 'D': 'disambiguation', 65 | 'C': 'category', 'N': 'name', 66 | 'E': 'exclusive', '': 'nothing'}.get(json.get('Type',''), '') 67 | 68 | self.json = json 69 | self.api_version = None # compat 70 | 71 | self.heading = json.get('Heading', '') 72 | 73 | self.results = [Result(elem) for elem in json.get('Results',[])] 74 | self.related = [Result(elem) for elem in 75 | json.get('RelatedTopics',[])] 76 | 77 | self.abstract = Abstract(json) 78 | self.redirect = Redirect(json) 79 | self.definition = Definition(json) 80 | self.answer = Answer(json) 81 | 82 | self.image = Image({'Result':json.get('Image','')}) 83 | 84 | 85 | class Abstract(object): 86 | 87 | def __init__(self, json): 88 | self.html = json.get('Abstract', '') 89 | self.text = json.get('AbstractText', '') 90 | self.url = json.get('AbstractURL', '') 91 | self.source = json.get('AbstractSource') 92 | 93 | class Redirect(object): 94 | 95 | def __init__(self, json): 96 | self.url = json.get('Redirect', '') 97 | 98 | class Result(object): 99 | 100 | def __init__(self, json): 101 | self.topics = json.get('Topics', []) 102 | if self.topics: 103 | self.topics = [Result(t) for t in self.topics] 104 | return 105 | self.html = json.get('Result') 106 | self.text = json.get('Text') 107 | self.url = json.get('FirstURL') 108 | 109 | icon_json = json.get('Icon') 110 | if icon_json is not None: 111 | self.icon = Image(icon_json) 112 | else: 113 | self.icon = None 114 | 115 | 116 | class Image(object): 117 | 118 | def __init__(self, json): 119 | self.url = json.get('Result') 120 | self.height = json.get('Height', None) 121 | self.width = json.get('Width', None) 122 | 123 | 124 | class Answer(object): 125 | 126 | def __init__(self, json): 127 | self.text = json.get('Answer') 128 | self.type = json.get('AnswerType', '') 129 | 130 | class Definition(object): 131 | def __init__(self, json): 132 | self.text = json.get('Definition','') 133 | self.url = json.get('DefinitionURL') 134 | self.source = json.get('DefinitionSource') 135 | 136 | 137 | def get_zci(q, web_fallback=True, priority=['answer', 'abstract', 'related.0', 'definition'], urls=True, **kwargs): 138 | '''A helper method to get a single (and hopefully the best) ZCI result. 139 | priority=list can be used to set the order in which fields will be checked for answers. 140 | Use web_fallback=True to fall back to grabbing the first web result. 141 | passed to query. This method will fall back to 'Sorry, no results.' 142 | if it cannot find anything.''' 143 | 144 | ddg = query('\\'+q, **kwargs) 145 | response = '' 146 | 147 | for p in priority: 148 | ps = p.split('.') 149 | type = ps[0] 150 | index = int(ps[1]) if len(ps) > 1 else None 151 | 152 | result = getattr(ddg, type) 153 | if index is not None: 154 | if not hasattr(result, '__getitem__'): raise TypeError('%s field is not indexable' % type) 155 | result = result[index] if len(result) > index else None 156 | if not result: continue 157 | 158 | if result.text: response = result.text 159 | if result.text and hasattr(result,'url') and urls: 160 | if result.url: response += ' (%s)' % result.url 161 | if response: break 162 | 163 | # if there still isn't anything, try to get the first web result 164 | if not response and web_fallback: 165 | if ddg.redirect.url: 166 | response = ddg.redirect.url 167 | 168 | # final fallback 169 | if not response: 170 | response = 'Sorry, no results.' 171 | 172 | return response 173 | 174 | def main(): 175 | if len(sys.argv) > 1: 176 | q = query(' '.join(sys.argv[1:])) 177 | keys = q.json.keys() 178 | keys.sort() 179 | for key in keys: 180 | sys.stdout.write(key) 181 | if type(q.json[key]) in [str,unicode,int]: print(':', q.json[key]) 182 | else: 183 | sys.stdout.write('\n') 184 | for i in q.json[key]: print('\t',i) 185 | else: 186 | print('Usage: %s [query]' % sys.argv[0]) 187 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup 2 | from duckduckgo import __version__ 3 | 4 | long_description = open('README.rst').read() 5 | 6 | setup(name='duckduckgo2', 7 | version=__version__, 8 | py_modules=['duckduckgo'], 9 | description='Library for querying the DuckDuckGo API', 10 | author='Michael Smith', 11 | author_email='crazedpsyc@duckduckgo.com', 12 | license='BSD', 13 | url='http://github.com/crazedpsyc/python-duckduckgo/', 14 | long_description=long_description, 15 | platforms=['any'], 16 | classifiers=["Development Status :: 4 - Beta", 17 | "Intended Audience :: Developers", 18 | "License :: OSI Approved :: BSD License", 19 | "Operating System :: OS Independent", 20 | "Programming Language :: Python", 21 | "Topic :: Internet :: WWW/HTTP :: Indexing/Search", 22 | ], 23 | entry_points={'console_scripts':['ddg = duckduckgo:main']}, 24 | ) 25 | --------------------------------------------------------------------------------