diff --git a/contributors.txt b/contributors.txt
index 53e917d..debc2ae 100755
--- a/contributors.txt
+++ b/contributors.txt
@@ -1,5 +1,8 @@
This file contains people who have helped xgoogle project:
+ * Nikola Milosevic
+ Thanks for adding:
+ * Face image search
* Holger Berndt
Thanks for adding:
* 'lang' and 'tld' arguments to Google Search
diff --git a/examples/ImageExample.py b/examples/ImageExample.py
new file mode 100644
index 0000000..3093f6e
--- /dev/null
+++ b/examples/ImageExample.py
@@ -0,0 +1,17 @@
+#
+# This program does a Google search for face images for "quick and dirty" and returns
+# 50 results.
+#
+
+from xgoogle.search import GoogleFaceImageSearch, SearchError
+try:
+ gs = GoogleFaceImageSearch("Eddard Stark")
+ gs.results_per_page = 50
+ results = gs.get_results()
+ for res in results:
+ print res.trumb.encode('utf8')
+ print res.url.encode('utf8')
+ print
+except SearchError, e:
+ print "Search failed: %s" % e
+
diff --git a/examples/ImageExample2.py b/examples/ImageExample2.py
new file mode 100644
index 0000000..afe37bb
--- /dev/null
+++ b/examples/ImageExample2.py
@@ -0,0 +1,17 @@
+#
+# This program does a Google search for images for "quick and dirty" and returns
+# 50 results.
+#
+
+from xgoogle.search import GoogleImageSearch, SearchError
+try:
+ gs = GoogleImageSearch("quick and dirty")
+ gs.results_per_page = 50
+ results = gs.get_results()
+ for res in results:
+ print res.trumb.encode('utf8')
+ print res.url.encode('utf8')
+ print
+except SearchError, e:
+ print "Search failed: %s" % e
+
diff --git a/examples/example1.py b/examples/example1.py
index 79e14cb..5121109 100755
--- a/examples/example1.py
+++ b/examples/example1.py
@@ -6,12 +6,13 @@
from xgoogle.search import GoogleSearch, SearchError
try:
- gs = GoogleSearch("quick and dirty")
+ gs = GoogleSearch("game of thrones season 3")
gs.results_per_page = 50
results = gs.get_results()
for res in results:
print res.title.encode('utf8')
print res.desc.encode('utf8')
+ print res.excerpt.encode('utf8')
print res.url.encode('utf8')
print
except SearchError, e:
diff --git a/examples/exampleVideoSearch.py b/examples/exampleVideoSearch.py
new file mode 100755
index 0000000..98910b3
--- /dev/null
+++ b/examples/exampleVideoSearch.py
@@ -0,0 +1,21 @@
+#
+# This program does a Google search for video for "Iron Maiden" and returns
+# 50 results. Video search requires NLTK. For instruction on installation please visit http://www.nltk.org/install.html
+#
+
+from xgoogle.search import GoogleVideoSearch, SearchError
+try:
+ gs = GoogleVideoSearch("Iron Maiden")
+ gs.results_per_page = 50
+ results = gs.get_results()
+ for res in results:
+ print 'Name: ' + res.name.encode('utf8')
+ print 'URL: ' + res.url.encode('utf8')
+ print 'Date: ' + res.date.encode('utf8')
+ print 'Duration: ' + res.duration.encode('utf8')
+ print 'Author: ' + res.author.encode('utf8')
+ print 'Description: ' + res.description.encode('utf8')
+ print
+except SearchError, e:
+ print "Search failed: %s" % e
+
diff --git a/readme.txt b/readme.txt
index 3835b39..2d4e929 100755
--- a/readme.txt
+++ b/readme.txt
@@ -1,7 +1,10 @@
-This is a Google library called 'xgoogle'. Current version is 1.3.
+This is a fork of a Google library called 'xgoogle'. Current version is 1.4
-It's written by Peteris Krumins (peter@catonmat.net).
-His blog is at http://www.catonmat.net -- good coders code, great reuse.
+It is forked by Nikola Milosevic (nikola.milosevic@inspiratron.org) from the original code that was written by Peteris Krumins (peter@catonmat.net).
+Peteris Krumins blog is at http://www.catonmat.net -- good coders code, great reuse.
+Nikola Milosevic's blog is at http://www.inspiratron.org.
+
+This fork adds Google face image search, and hopefully in the future Google image search
The code is licensed under MIT license.
@@ -187,13 +190,12 @@ v1.1: * added Google Sponsored Links Search.
v1.2: * added Google Sets module
v1.3: * added Google Translate module
* fixed a bug in browser.py when KeyboardInterrupt did not get propagated.
+v1.4: * added Google image and face image search
+ * added Google video search (requires NLTK, for install instruction see http://www.nltk.org/install.html)
--------------------------------------------------------------------------
That's it. Have fun! :)
-Sincerely,
-Peteris Krumins
-http://www.catonmat.net
diff --git a/setup.py b/setup.py
index 1027405..6f79f79 100644
--- a/setup.py
+++ b/setup.py
@@ -1,7 +1,7 @@
from setuptools import setup, find_packages
import sys
-__version__ = '1.3'
+__version__ = '1.4'
import os
def _read(fname):
@@ -14,8 +14,8 @@ def _read(fname):
long_description=_read('readme.txt'),
classifiers=[],
keywords='google search',
- author='Peteris Krumins',
- author_email='peter@catonmat.net',
+ author='Peteris Krumins, Nikola Milosevic',
+ author_email='nikola.milosevic@inspiratron.org',
url='http://github.com/pkrumins/xgoogle',
license='MIT',
packages=find_packages(exclude=['ez_setup', 'examples', 'tests']),
@@ -25,6 +25,7 @@ def _read(fname):
include_package_data=True,
zip_safe=False,
install_requires=[
+ 'nltk==2.0.4'
# -*- Extra requirements: -*-
],
)
diff --git a/xgoogle/search.py b/xgoogle/search.py
index 98b681e..4c3283d 100755
--- a/xgoogle/search.py
+++ b/xgoogle/search.py
@@ -3,6 +3,8 @@
#
# Peteris Krumins (peter@catonmat.net)
# http://www.catonmat.net -- good coders code, great reuse
+# Updated by Nikola Milosevic (nikola.milosevic@inspiratron.org
+# http://www.inspiratron.org
#
# http://www.catonmat.net/blog/python-library-for-google-search/
#
@@ -13,6 +15,7 @@
import urllib
from htmlentitydefs import name2codepoint
from BeautifulSoup import BeautifulSoup
+import nltk
from browser import Browser, BrowserError
@@ -39,21 +42,596 @@ def __str__(self):
def html(self):
return self.tag.prettify()
+
+
+# videoname = nltk.clean_html(str(h3[0]))
+# video_url = result.findAll('cite')
+# date_and_author = result.find('div',{'class':'f slp'})
+# da = str.split(str(date_and_author,' - Uploaded by'))
+# date = da[0]
+# author = da[1]
+# desc = result.find('span',{'class':'st'})
+# description = nltk.clean_html(str(desc))
+class FaceVideoSearchResult:
+ def __init__(self, name, url, description,date,duration,author):
+ self.name = name
+ self.url = url
+ self.description = description
+ self.date = date
+ self.duration = duration
+ self.author= author
+
+ def __str__(self):
+ return 'Google Search Result: "%s"' % self.name
+
+class FaceImageSearchResult:
+ def __init__(self, trumb, url):
+ self.url = url
+ self.trumb = trumb
+
+ def __str__(self):
+ return 'Google Search Result: "%s"' % self.trumb
class SearchResult:
- def __init__(self, title, url, desc):
+ def __init__(self, title, url, desc,excerpt):
self.title = title
self.url = url
self.desc = desc
+ self.excerpt = excerpt
+
+ def __str__(self):
+ return 'Google Search Result: "%s"' % self.title
+
+class GoogleSearch(object):
+ SEARCH_URL_0 = "http://www.google.%(tld)s/search?hl=%(lang)s&q=%(query)s&btnG=Google+Search"
+ NEXT_PAGE_0 = "http://www.google.%(tld)s/search?hl=%(lang)s&q=%(query)s&start=%(start)d"
+ SEARCH_URL_1 = "http://www.google.%(tld)s/search?hl=%(lang)s&q=%(query)s&num=%(num)d&btnG=Google+Search"
+ NEXT_PAGE_1 = "http://www.google.%(tld)s/search?hl=%(lang)s&q=%(query)s&num=%(num)d&start=%(start)d"
+
+ def __init__(self, query, random_agent=False, debug=False, lang="en", tld="com", re_search_strings=None):
+ self.query = query
+ self.debug = debug
+ self.browser = Browser(debug=debug)
+ self.results_info = None
+ self.eor = False # end of results
+ self._page = 0
+ self._first_indexed_in_previous = None
+ self._filetype = None
+ self._last_search_url = None
+ self._results_per_page = 10
+ self._last_from = 0
+ self._lang = lang
+ self._tld = tld
+
+ if re_search_strings:
+ self._re_search_strings = re_search_strings
+ elif lang == "de":
+ self._re_search_strings = ("Ergebnisse", "von", u"ungefähr")
+ elif lang == "es":
+ self._re_search_strings = ("Resultados", "de", "aproximadamente")
+ # add more localised versions here
+ else:
+ self._re_search_strings = ("Results", "of", "about")
+
+ if random_agent:
+ self.browser.set_random_user_agent()
+
+ @property
+ def num_results(self):
+ if not self.results_info:
+ page = self._get_results_page()
+ self.results_info = self._extract_info(page)
+ if self.results_info['total'] == 0:
+ self.eor = True
+ return self.results_info['total']
+
+ @property
+ def last_search_url(self):
+ return self._last_search_url
+
+ def _get_page(self):
+ return self._page
+
+ def _set_page(self, page):
+ self._page = page
+
+ page = property(_get_page, _set_page)
+
+ def _get_first_indexed_in_previous(self):
+ return self._first_indexed_in_previous
+
+ def _set_first_indexed_in_previous(self, interval):
+ if interval == "day":
+ self._first_indexed_in_previous = 'd'
+ elif interval == "week":
+ self._first_indexed_in_previous = 'w'
+ elif interval == "month":
+ self._first_indexed_in_previous = 'm'
+ elif interval == "year":
+ self._first_indexed_in_previous = 'y'
+ else:
+ # a floating point value is a number of months
+ try:
+ num = float(interval)
+ except ValueError:
+ raise SearchError, "Wrong parameter to first_indexed_in_previous: %s" % (str(interval))
+ self._first_indexed_in_previous = 'm' + str(interval)
+
+ first_indexed_in_previous = property(_get_first_indexed_in_previous, _set_first_indexed_in_previous, doc="possible values: day, week, month, year, or a float value of months")
+
+ def _get_filetype(self):
+ return self._filetype
+
+ def _set_filetype(self, filetype):
+ self._filetype = filetype
+
+ filetype = property(_get_filetype, _set_filetype, doc="file extension to search for")
+
+ def _get_results_per_page(self):
+ return self._results_per_page
+
+ def _set_results_par_page(self, rpp):
+ self._results_per_page = rpp
+
+ results_per_page = property(_get_results_per_page, _set_results_par_page)
+
+ def get_results(self):
+ """ Gets a page of results """
+ if self.eor:
+ return []
+ MAX_VALUE = 1000000
+ page = self._get_results_page()
+ #search_info = self._extract_info(page)
+ results = self._extract_results(page)
+ search_info = {'from': self.results_per_page*self._page,
+ 'to': self.results_per_page*self._page + len(results),
+ 'total': MAX_VALUE}
+ if not self.results_info:
+ self.results_info = search_info
+ if self.num_results == 0:
+ self.eor = True
+ return []
+ if not results:
+ self.eor = True
+ return []
+ if self._page > 0 and search_info['from'] == self._last_from:
+ self.eor = True
+ return []
+ if search_info['to'] == search_info['total']:
+ self.eor = True
+ self._page += 1
+ self._last_from = search_info['from']
+ return results
+
+ def _maybe_raise(self, cls, *arg):
+ if self.debug:
+ raise cls(*arg)
+
+ def _get_results_page(self):
+ if self._page == 0:
+ if self._results_per_page == 10:
+ url = GoogleSearch.SEARCH_URL_0
+ else:
+ url = GoogleSearch.SEARCH_URL_1
+ else:
+ if self._results_per_page == 10:
+ url = GoogleSearch.NEXT_PAGE_0
+ else:
+ url = GoogleSearch.NEXT_PAGE_1
+
+ safe_url = [url % { 'query': urllib.quote_plus(self.query),
+ 'start': self._page * self._results_per_page,
+ 'num': self._results_per_page,
+ 'tld' : self._tld,
+ 'lang' : self._lang }]
+
+ # possibly extend url with optional properties
+ if self._first_indexed_in_previous:
+ safe_url.extend(["&as_qdr=", self._first_indexed_in_previous])
+ if self._filetype:
+ safe_url.extend(["&as_filetype=", self._filetype])
+
+ safe_url = "".join(safe_url)
+ self._last_search_url = safe_url
+
+ try:
+ page = self.browser.get_page(safe_url)
+ except BrowserError, e:
+ raise SearchError, "Failed getting %s: %s" % (e.url, e.error)
+
+ return BeautifulSoup(page)
+
+ def _extract_info(self, soup):
+ empty_info = {'from': 0, 'to': 0, 'total': 0}
+ div_ssb = soup.find('div', id='ssb')
+ if not div_ssb:
+ self._maybe_raise(ParseError, "Div with number of results was not found on Google search page", soup)
+ return empty_info
+ p = div_ssb.find('p')
+ if not p:
+ self._maybe_raise(ParseError, """
tag within
was not found on Google search page""", soup)
+ return empty_info
+ txt = ''.join(p.findAll(text=True))
+ txt = txt.replace(',', '')
+ matches = re.search(r'%s (\d+) - (\d+) %s (?:%s )?(\d+)' % self._re_search_strings, txt, re.U)
+ if not matches:
+ return empty_info
+ return {'from': int(matches.group(1)), 'to': int(matches.group(2)), 'total': int(matches.group(3))}
+
+ def _extract_results(self, soup):
+ results = soup.findAll('li', {'class': 'g'})
+ ret_res = []
+ for result in results:
+ eres = self._extract_result(result)
+ if eres:
+ ret_res.append(eres)
+ return ret_res
+
+ def _extract_result(self, result):
+ title, url = self._extract_title_url(result)
+ desc = self._extract_description(result)
+ if desc == None:
+ desc = ''
+ excerpt = self._extract_excerpt(result)
+ if not title or not url or not (desc or excerpt):
+ return None
+ return SearchResult(title, url, desc,excerpt)
+
+ def _extract_title_url(self, result):
+ #title_a = result.find('a', {'class': re.compile(r'\bl\b')})
+ title_a = result.find('a')
+ if not title_a:
+ self._maybe_raise(ParseError, "Title tag in Google search result was not found", result)
+ return None, None
+ title = ''.join(title_a.findAll(text=True))
+ title = self._html_unescape(title)
+ url = title_a['href']
+ match = re.match(r'/url\?q=(http[^&]+)&', url)
+ if match:
+ url = urllib.unquote(match.group(1))
+ return title, url
+
+ def _extract_excerpt(self, result):
+ def looper(tag):
+ if not tag: return
+ for t in tag:
+ try:
+ if t.name == 'br': pass
+ except AttributeError:
+ pass
+ try:
+ desc_strs.append(t.string)
+ except AttributeError:
+ desc_strs.append(t)
+ desc_div = result.find('span', {'class': re.compile(r'\bst\b')})
+ if not desc_div:
+ self._maybe_raise(ParseError, "Content excerpt tag in Google search result was not found", result)
+ return None
+ desc_strs = []
+
+ looper(desc_div)
+ looper(desc_div.find('wbr')) # BeautifulSoup does not self-close
+
+ desc = ''.join(s for s in desc_strs if s)
+ return self._html_unescape(desc)
+
+ def _extract_description(self, result):
+ desc_div = result.find('div', {'class': re.compile(r'\bs\b')})
+ if not desc_div:
+ self._maybe_raise(ParseError, "Description tag in Google search result was not found", result)
+ return None
+
+ desc_strs = []
+ def looper(tag):
+ if not tag: return
+ for t in tag:
+ try:
+ if t.name == 'br': break
+ except AttributeError:
+ pass
+
+ try:
+ desc_strs.append(t.string)
+ except AttributeError:
+ desc_strs.append(t)
+
+ looper(desc_div)
+ looper(desc_div.find('wbr')) # BeautifulSoup does not self-close
+
+ desc = ''.join(s for s in desc_strs if s)
+ return self._html_unescape(desc)
+
+ def _html_unescape(self, str):
+ def entity_replacer(m):
+ entity = m.group(1)
+ if entity in name2codepoint:
+ return unichr(name2codepoint[entity])
+ else:
+ return m.group(0)
+
+ def ascii_replacer(m):
+ cp = int(m.group(1))
+ if cp <= 255:
+ return unichr(cp)
+ else:
+ return m.group(0)
+
+ s = re.sub(r'(\d+);', ascii_replacer, str, re.U)
+ return re.sub(r'&([^;]+);', entity_replacer, s, re.U)
+
+class GoogleVideoSearch(object):
+ SEARCH_URL_0 = "http://www.google.%(tld)s/search?tbm=vid&hl=%(lang)s&q=%(query)s"
+ NEXT_PAGE_0 = "http://www.google.%(tld)s/search?tbm=vid&hl=%(lang)s&q=%(query)s&start=%(start)d"
+ SEARCH_URL_1 = "http://www.google.%(tld)s/search?tbm=vid&hl=%(lang)s&q=%(query)s&num=%(num)d"
+ NEXT_PAGE_1 = "http://www.google.%(tld)s/search?tbm=vid&hl=%(lang)s&q=%(query)s&num=%(num)d&start=%(start)d"
+
+ def __init__(self, query, random_agent=False, debug=False, lang="en", tld="com", re_search_strings=None):
+ self.query = query
+ self.debug = debug
+ self.browser = Browser(debug=debug)
+ self.results_info = None
+ self.eor = False # end of results
+ self._page = 0
+ self._first_indexed_in_previous = None
+ self._filetype = None
+ self._last_search_url = None
+ self._results_per_page = 10
+ self._last_from = 0
+ self._lang = lang
+ self._tld = tld
+
+ if re_search_strings:
+ self._re_search_strings = re_search_strings
+ elif lang == "de":
+ self._re_search_strings = ("Ergebnisse", "von", u"ungefähr")
+ elif lang == "es":
+ self._re_search_strings = ("Resultados", "de", "aproximadamente")
+ # add more localised versions here
+ else:
+ self._re_search_strings = ("Results", "of", "about")
+
+ if random_agent:
+ self.browser.set_random_user_agent()
+
+ @property
+ def num_results(self):
+ if not self.results_info:
+ page = self._get_results_page()
+ self.results_info = self._extract_info(page)
+ if self.results_info['total'] == 0:
+ self.eor = True
+ return self.results_info['total']
+
+ @property
+ def last_search_url(self):
+ return self._last_search_url
+
+ def _get_page(self):
+ return self._page
+
+ def _set_page(self, page):
+ self._page = page
+
+ page = property(_get_page, _set_page)
+
+ def _get_first_indexed_in_previous(self):
+ return self._first_indexed_in_previous
+
+ def _set_first_indexed_in_previous(self, interval):
+ if interval == "day":
+ self._first_indexed_in_previous = 'd'
+ elif interval == "week":
+ self._first_indexed_in_previous = 'w'
+ elif interval == "month":
+ self._first_indexed_in_previous = 'm'
+ elif interval == "year":
+ self._first_indexed_in_previous = 'y'
+ else:
+ # a floating point value is a number of months
+ try:
+ num = float(interval)
+ except ValueError:
+ raise SearchError, "Wrong parameter to first_indexed_in_previous: %s" % (str(interval))
+ self._first_indexed_in_previous = 'm' + str(interval)
+
+ first_indexed_in_previous = property(_get_first_indexed_in_previous, _set_first_indexed_in_previous, doc="possible values: day, week, month, year, or a float value of months")
+
+ def _get_filetype(self):
+ return self._filetype
+
+ def _set_filetype(self, filetype):
+ self._filetype = filetype
+
+ filetype = property(_get_filetype, _set_filetype, doc="file extension to search for")
+
+ def _get_results_per_page(self):
+ return self._results_per_page
+
+ def _set_results_par_page(self, rpp):
+ self._results_per_page = rpp
+
+ results_per_page = property(_get_results_per_page, _set_results_par_page)
+
+ def get_results(self):
+ """ Gets a page of results """
+ if self.eor:
+ return []
+ MAX_VALUE = 1000000
+ page = self._get_results_page()
+ results = self._extract_results(page)
+ search_info = {'from': self.results_per_page*self._page,
+ 'to': self.results_per_page*self._page + len(results),
+ 'total': MAX_VALUE}
+ if not self.results_info:
+ self.results_info = search_info
+ if self.num_results == 0:
+ self.eor = True
+ return []
+ if not results:
+ self.eor = True
+ return []
+ if self._page > 0 and search_info['from'] == self._last_from:
+ self.eor = True
+ return []
+ if search_info['to'] == search_info['total']:
+ self.eor = True
+ self._page += 1
+ self._last_from = search_info['from']
+ return results
+
+ def _maybe_raise(self, cls, *arg):
+ if self.debug:
+ raise cls(*arg)
+
+ def _get_results_page(self):
+ if self._page == 0:
+ if self._results_per_page == 10:
+ url = GoogleVideoSearch.SEARCH_URL_0
+ else:
+ url = GoogleVideoSearch.SEARCH_URL_1
+ else:
+ if self._results_per_page == 10:
+ url = GoogleVideoSearch.NEXT_PAGE_0
+ else:
+ url = GoogleVideoSearch.NEXT_PAGE_1
+
+ safe_url = [url % { 'query': urllib.quote_plus(self.query),
+ 'start': self._page * self._results_per_page,
+ 'num': self._results_per_page,
+ 'tld' : self._tld,
+ 'lang' : self._lang }]
+
+ # possibly extend url with optional properties
+ if self._first_indexed_in_previous:
+ safe_url.extend(["&as_qdr=", self._first_indexed_in_previous])
+ if self._filetype:
+ safe_url.extend(["&as_filetype=", self._filetype])
+
+ safe_url = "".join(safe_url)
+ self._last_search_url = safe_url
+
+ try:
+ page = self.browser.get_page(safe_url)
+ except BrowserError, e:
+ raise SearchError, "Failed getting %s: %s" % (e.url, e.error)
+
+ return BeautifulSoup(page)
+
+ def _extract_info(self, soup):
+ empty_info = {'from': 0, 'to': 0, 'total': 0}
+ div_ssb = soup.find('div', id='ssb')
+ if not div_ssb:
+ self._maybe_raise(ParseError, "Div with number of results was not found on Google search page", soup)
+ return empty_info
+ p = div_ssb.find('p')
+ if not p:
+ self._maybe_raise(ParseError, """ tag within
was not found on Google search page""", soup)
+ return empty_info
+ txt = ''.join(p.findAll(text=True))
+ txt = txt.replace(',', '')
+ matches = re.search(r'%s (\d+) - (\d+) %s (?:%s )?(\d+)' % self._re_search_strings, txt, re.U)
+ if not matches:
+ return empty_info
+ return {'from': int(matches.group(1)), 'to': int(matches.group(2)), 'total': int(matches.group(3))}
+
+ def _extract_results(self, soup):
+ results = soup.findAll('li', {"class" : re.compile(r'\b(g videobox|g)\b')})
+ ret_res = []
+ for result in results:
+ eres = self._extract_result(result)
+ if eres:
+ ret_res.append(eres)
+ return ret_res
+
+ def _extract_result(self, result):
+
+ h3=result.findAll('h3')
+ name = ''
+ for lonuri in h3:
+ name = name + str(lonuri)
+ videoname = nltk.clean_html(str(name))
+ video_url = BeautifulSoup(str(h3)).findAll('a')
+ url = str.split(video_url[0]['href'][7:].encode('utf8'),'&')[0]
+ url = url.replace('%3F', '?')
+ url = url.replace('%3D', '=')
+ desc = result.find('span',{'class':'st'})
+ meta = result.find('span',{'class':'f'})
+ author = ''
+ duration = ''
+ date = ''
+ if(not meta== None):
+ metastr= nltk.clean_html(str(meta))
+ metaarr = metastr.split('-')
+ date = metaarr[0]
+ duration = metaarr[1]
+ author = metaarr[2][13:]
+ description = nltk.clean_html(str(desc))
+ return FaceVideoSearchResult(videoname,url,description,date,duration,author)
+ #return FaceImageSearchResult(trumnail, image)
+
+ def _extract_title_url(self, result):
+ #title_a = result.find('a', {'class': re.compile(r'\bl\b')})
+ title_a = result.find('a')
+ if not title_a:
+ self._maybe_raise(ParseError, "Title tag in Google search result was not found", result)
+ return None, None
+ title = ''.join(title_a.findAll(text=True))
+ title = self._html_unescape(title)
+ url = title_a['href']
+ match = re.match(r'/url\?q=(http[^&]+)&', url)
+ if match:
+ url = urllib.unquote(match.group(1))
+ return title, url
+
+ def _extract_description(self, result):
+ desc_div = result.find('div', {'class': re.compile(r'\bs\b')})
+ if not desc_div:
+ self._maybe_raise(ParseError, "Description tag in Google search result was not found", result)
+ return None
+
+ desc_strs = []
+ def looper(tag):
+ if not tag: return
+ for t in tag:
+ try:
+ if t.name == 'br': break
+ except AttributeError:
+ pass
+
+ try:
+ desc_strs.append(t.string)
+ except AttributeError:
+ desc_strs.append(t)
+
+ looper(desc_div)
+ looper(desc_div.find('wbr')) # BeautifulSoup does not self-close
+
+ desc = ''.join(s for s in desc_strs if s)
+ return self._html_unescape(desc)
+
+ def _html_unescape(self, str):
+ def entity_replacer(m):
+ entity = m.group(1)
+ if entity in name2codepoint:
+ return unichr(name2codepoint[entity])
+ else:
+ return m.group(0)
+
+ def ascii_replacer(m):
+ cp = int(m.group(1))
+ if cp <= 255:
+ return unichr(cp)
+ else:
+ return m.group(0)
- def __str__(self):
- return 'Google Search Result: "%s"' % self.title
+ s = re.sub(r'(\d+);', ascii_replacer, str, re.U)
+ return re.sub(r'&([^;]+);', entity_replacer, s, re.U)
-class GoogleSearch(object):
- SEARCH_URL_0 = "http://www.google.%(tld)s/search?hl=%(lang)s&q=%(query)s&btnG=Google+Search"
- NEXT_PAGE_0 = "http://www.google.%(tld)s/search?hl=%(lang)s&q=%(query)s&start=%(start)d"
- SEARCH_URL_1 = "http://www.google.%(tld)s/search?hl=%(lang)s&q=%(query)s&num=%(num)d&btnG=Google+Search"
- NEXT_PAGE_1 = "http://www.google.%(tld)s/search?hl=%(lang)s&q=%(query)s&num=%(num)d&start=%(start)d"
+class GoogleImageSearch(object):
+ SEARCH_URL_0 = "http://www.google.%(tld)s/search?tbm=isch&hl=%(lang)s&q=%(query)s"
+ NEXT_PAGE_0 = "http://www.google.%(tld)s/search?tbm=isch&hl=%(lang)s&q=%(query)s&start=%(start)d"
+ SEARCH_URL_1 = "http://www.google.%(tld)s/search?tbm=isch&hl=%(lang)s&q=%(query)s&num=%(num)d"
+ NEXT_PAGE_1 = "http://www.google.%(tld)s/search?tbm=isch&hl=%(lang)s&q=%(query)s&num=%(num)d&start=%(start)d"
def __init__(self, query, random_agent=False, debug=False, lang="en", tld="com", re_search_strings=None):
self.query = query
@@ -148,7 +726,6 @@ def get_results(self):
return []
MAX_VALUE = 1000000
page = self._get_results_page()
- #search_info = self._extract_info(page)
results = self._extract_results(page)
search_info = {'from': self.results_per_page*self._page,
'to': self.results_per_page*self._page + len(results),
@@ -177,14 +754,14 @@ def _maybe_raise(self, cls, *arg):
def _get_results_page(self):
if self._page == 0:
if self._results_per_page == 10:
- url = GoogleSearch.SEARCH_URL_0
+ url = GoogleImageSearch.SEARCH_URL_0
else:
- url = GoogleSearch.SEARCH_URL_1
+ url = GoogleImageSearch.SEARCH_URL_1
else:
if self._results_per_page == 10:
- url = GoogleSearch.NEXT_PAGE_0
+ url = GoogleImageSearch.NEXT_PAGE_0
else:
- url = GoogleSearch.NEXT_PAGE_1
+ url = GoogleImageSearch.NEXT_PAGE_1
safe_url = [url % { 'query': urllib.quote_plus(self.query),
'start': self._page * self._results_per_page,
@@ -226,7 +803,9 @@ def _extract_info(self, soup):
return {'from': int(matches.group(1)), 'to': int(matches.group(2)), 'total': int(matches.group(3))}
def _extract_results(self, soup):
- results = soup.findAll('li', {'class': 'g'})
+ # Should extract
+
+ desc = ''.join(s for s in desc_strs if s)
+ return self._html_unescape(desc)
+
+ def _html_unescape(self, str):
+ def entity_replacer(m):
+ entity = m.group(1)
+ if entity in name2codepoint:
+ return unichr(name2codepoint[entity])
+ else:
+ return m.group(0)
+
+ def ascii_replacer(m):
+ cp = int(m.group(1))
+ if cp <= 255:
+ return unichr(cp)
+ else:
+ return m.group(0)
+
+ s = re.sub(r'(\d+);', ascii_replacer, str, re.U)
+ return re.sub(r'&([^;]+);', entity_replacer, s, re.U)
+
+
+class GoogleFaceImageSearch(object):
+ SEARCH_URL_0 = "http://www.google.%(tld)s/search?tbm=isch&tbs=itp:face&hl=%(lang)s&q=%(query)s"
+ NEXT_PAGE_0 = "http://www.google.%(tld)s/search?tbm=isch&tbs=itp:face&hl=%(lang)s&q=%(query)s&start=%(start)d"
+ SEARCH_URL_1 = "http://www.google.%(tld)s/search?tbm=isch&tbs=itp:face&hl=%(lang)s&q=%(query)s&num=%(num)d"
+ NEXT_PAGE_1 = "http://www.google.%(tld)s/search?tbm=isch&tbs=itp:face&hl=%(lang)s&q=%(query)s&num=%(num)d&start=%(start)d"
+
+ def __init__(self, query, random_agent=False, debug=False, lang="en", tld="com", re_search_strings=None):
+ self.query = query
+ self.debug = debug
+ self.browser = Browser(debug=debug)
+ self.results_info = None
+ self.eor = False # end of results
+ self._page = 0
+ self._first_indexed_in_previous = None
+ self._filetype = None
+ self._last_search_url = None
+ self._results_per_page = 10
+ self._last_from = 0
+ self._lang = lang
+ self._tld = tld
+
+ if re_search_strings:
+ self._re_search_strings = re_search_strings
+ elif lang == "de":
+ self._re_search_strings = ("Ergebnisse", "von", u"ungefähr")
+ elif lang == "es":
+ self._re_search_strings = ("Resultados", "de", "aproximadamente")
+ # add more localised versions here
+ else:
+ self._re_search_strings = ("Results", "of", "about")
+
+ if random_agent:
+ self.browser.set_random_user_agent()
+
+ @property
+ def num_results(self):
+ if not self.results_info:
+ page = self._get_results_page()
+ self.results_info = self._extract_info(page)
+ if self.results_info['total'] == 0:
+ self.eor = True
+ return self.results_info['total']
+
+ @property
+ def last_search_url(self):
+ return self._last_search_url
+
+ def _get_page(self):
+ return self._page
+
+ def _set_page(self, page):
+ self._page = page
+
+ page = property(_get_page, _set_page)
+
+ def _get_first_indexed_in_previous(self):
+ return self._first_indexed_in_previous
+
+ def _set_first_indexed_in_previous(self, interval):
+ if interval == "day":
+ self._first_indexed_in_previous = 'd'
+ elif interval == "week":
+ self._first_indexed_in_previous = 'w'
+ elif interval == "month":
+ self._first_indexed_in_previous = 'm'
+ elif interval == "year":
+ self._first_indexed_in_previous = 'y'
+ else:
+ # a floating point value is a number of months
+ try:
+ num = float(interval)
+ except ValueError:
+ raise SearchError, "Wrong parameter to first_indexed_in_previous: %s" % (str(interval))
+ self._first_indexed_in_previous = 'm' + str(interval)
+
+ first_indexed_in_previous = property(_get_first_indexed_in_previous, _set_first_indexed_in_previous, doc="possible values: day, week, month, year, or a float value of months")
+
+ def _get_filetype(self):
+ return self._filetype
+
+ def _set_filetype(self, filetype):
+ self._filetype = filetype
+
+ filetype = property(_get_filetype, _set_filetype, doc="file extension to search for")
+
+ def _get_results_per_page(self):
+ return self._results_per_page
+
+ def _set_results_par_page(self, rpp):
+ self._results_per_page = rpp
+
+ results_per_page = property(_get_results_per_page, _set_results_par_page)
+
+ def get_results(self):
+ """ Gets a page of results """
+ if self.eor:
+ return []
+ MAX_VALUE = 1000000
+ page = self._get_results_page()
+ results = self._extract_results(page)
+ search_info = {'from': self.results_per_page*self._page,
+ 'to': self.results_per_page*self._page + len(results),
+ 'total': MAX_VALUE}
+ if not self.results_info:
+ self.results_info = search_info
+ if self.num_results == 0:
+ self.eor = True
+ return []
+ if not results:
+ self.eor = True
+ return []
+ if self._page > 0 and search_info['from'] == self._last_from:
+ self.eor = True
+ return []
+ if search_info['to'] == search_info['total']:
+ self.eor = True
+ self._page += 1
+ self._last_from = search_info['from']
+ return results
+
+ def _maybe_raise(self, cls, *arg):
+ if self.debug:
+ raise cls(*arg)
+
+ def _get_results_page(self):
+ if self._page == 0:
+ if self._results_per_page == 10:
+ url = GoogleFaceImageSearch.SEARCH_URL_0
+ else:
+ url = GoogleFaceImageSearch.SEARCH_URL_1
+ else:
+ if self._results_per_page == 10:
+ url = GoogleFaceImageSearch.NEXT_PAGE_0
+ else:
+ url = GoogleFaceImageSearch.NEXT_PAGE_1
+
+ safe_url = [url % { 'query': urllib.quote_plus(self.query),
+ 'start': self._page * self._results_per_page,
+ 'num': self._results_per_page,
+ 'tld' : self._tld,
+ 'lang' : self._lang }]
+
+ # possibly extend url with optional properties
+ if self._first_indexed_in_previous:
+ safe_url.extend(["&as_qdr=", self._first_indexed_in_previous])
+ if self._filetype:
+ safe_url.extend(["&as_filetype=", self._filetype])
+
+ safe_url = "".join(safe_url)
+ self._last_search_url = safe_url
+
+ try:
+ page = self.browser.get_page(safe_url)
+ except BrowserError, e:
+ raise SearchError, "Failed getting %s: %s" % (e.url, e.error)
+
+ return BeautifulSoup(page)
+
+ def _extract_info(self, soup):
+ empty_info = {'from': 0, 'to': 0, 'total': 0}
+ div_ssb = soup.find('div', id='ssb')
+ if not div_ssb:
+ self._maybe_raise(ParseError, "Div with number of results was not found on Google search page", soup)
+ return empty_info
+ p = div_ssb.find('p')
+ if not p:
+ self._maybe_raise(ParseError, """ tag within
was not found on Google search page""", soup)
+ return empty_info
+ txt = ''.join(p.findAll(text=True))
+ txt = txt.replace(',', '')
+ matches = re.search(r'%s (\d+) - (\d+) %s (?:%s )?(\d+)' % self._re_search_strings, txt, re.U)
+ if not matches:
+ return empty_info
+ return {'from': int(matches.group(1)), 'to': int(matches.group(2)), 'total': int(matches.group(3))}
+
+ def _extract_results(self, soup):
+ # Should extract