diff --git a/APIUtils.py b/APIUtils.py index 774fcb7..b1a0a07 100644 --- a/APIUtils.py +++ b/APIUtils.py @@ -11,7 +11,6 @@ import urllib import AppConfig from google.appengine.api import urlfetch -import DataCache from BeautifulSoup import BeautifulSoup from django.utils import simplejson from structured import list2xml @@ -22,243 +21,40 @@ def removeHtmlTags(data): def removeNonAscii(s): return "" . join(filter(lambda x: ord(x)<128, s)) -#get cached content -def getCache(pageId,format): - logging.debug('getCache: %s' % pageId) - dbData = DataCache.getData(pageId,format) - if (dbData): - if (DataCache.hasExpired(dbData)): - #data has expired, remove it - dbData[0].delete() - return None - else: - logging.debug('getCache: got cached data for id %s' % id) - return dbData[0].rec_xml - -#parse HN's submissions by user -def getHackerNewsSubmittedContent(user, format='json', url='', referer='', remote_addr=''): - #only cache homepage data - apiURL = "%s/submitted?id=%s" % (AppConfig.hackerNewsURL, user) - apiURLBackup = "%s/submitted?id=%s" % (AppConfig.hackerNewsURLBackup, user) - id = '/submitted/%s' % (user) - cachedData = getCache(id,format) - if (cachedData): - return cachedData +#call urlfetch to get remote data +def fetchRemoteData(urlStr, deadline): + result = urlfetch.fetch(url=urlStr, deadline=deadline) + if result.status_code == 200: + return result else: - hnData = parsePageContent(apiURL,apiURLBackup, '/submitted', None,format) - if (hnData): - logging.debug('getHackerNewsSubmittedContent: storing cached value for id %s' % id) - DataCache.putData(id, format,removeNonAscii(hnData), url, referer, remote_addr) - return hnData - else: - logging.warning('getHackerNewsSubmittedContent: unable to retrieve data for id %s' % id) - return '' - -#parse HN's comments by story id -def getHackerNewsComments(articleId, format='json', url='', referer='', remote_addr=''): - #only cache homepage data - apiURL = "%s/item?id=%s" % (AppConfig.hackerNewsURL, articleId) - apiURLBackup = "%s/item?id=%s" % (AppConfig.hackerNewsURLBackup, articleId) - id = '/comments/%s' % (articleId) - cachedData = getCache(id,format) - if (cachedData): - return cachedData - else: - hnData = parseCommentsContent(apiURL, apiURLBackup, '/comments', None,format) - if (hnData): - logging.debug('getHackerNewsComments: storing cached value for id %s' % id) - DataCache.putData(id, format,removeNonAscii(hnData), url, referer, remote_addr) - return hnData - else: - logging.warning('getHackerNewsComments: unable to retrieve data for id %s' % id) - return '' - -#parse HN's comments by story id -def getHackerNewsNestedComments(articleId, format='json', url='', referer='', remote_addr=''): - #only cache homepage data - apiURL = "%s/item?id=%s" % (AppConfig.hackerNewsURL, articleId) - apiURLBackup = "%s/item?id=%s" % (AppConfig.hackerNewsURLBackup, articleId) - id = '/nestedcomments/%s' % (articleId) - cachedData = getCache(id,format) - if (cachedData): - return cachedData - else: - hnData = parseNestedCommentsContent(apiURL, apiURLBackup, '/nestedcomments', None,format) - if (hnData): - logging.debug('getHackerNewsComments: storing cached value for id %s' % id) - DataCache.putData(id, format,removeNonAscii(hnData), url, referer, remote_addr) - return hnData - else: - logging.warning('getHackerNewsComments: unable to retrieve data for id %s' % id) - return '' - -#parse HN's ask content -def getHackerNewsAskContent(page='', format='json', url='', referer='', remote_addr=''): - #only cache homepage data - if (page): - return parsePageContent(AppConfig.hackerNewsAskURL, AppConfig.hackerNewsAskURLBackup, '/ask', page,format) - else: - id = '/ask/' - cachedData = getCache(id,format) - if (cachedData): - return cachedData - else: - hnData = parsePageContent(AppConfig.hackerNewsAskURL, AppConfig.hackerNewsAskURLBackup, '/ask', page,format) - if (hnData): - logging.debug('getCache: storing cached value for id %s' % id) - DataCache.putData(id, format,removeNonAscii(hnData), url, referer, remote_addr) - return hnData - else: - logging.warning('getCache: unable to retrieve data for id %s' % id) - return '' - -#parse HN's best content -def getHackerNewsBestContent(page='', format='json', url='', referer='', remote_addr=''): - #only cache homepage data - if (page): - return parsePageContent(AppConfig.hackerNewsBestURL, AppConfig.hackerNewsBestURLBackup, '/best', page,format) - else: - id = '/best/' - cachedData = getCache(id,format) - if (cachedData): - return cachedData - else: - hnData = parsePageContent(AppConfig.hackerNewsBestURL, AppConfig.hackerNewsBestURLBackup, '/best', page,format) - if (hnData): - logging.debug('getCache: storing cached value for id %s' % id) - DataCache.putData(id, format,removeNonAscii(hnData), url, referer, remote_addr) - return hnData - else: - logging.warning('getCache: unable to retrieve data for id %s' % id) - return '' - -#parse HN's newest content -def getHackerNewsNewestContent(page='', format='json', url='', referer='', remote_addr=''): - #only cache homepage data - if (page): - return parsePageContent(AppConfig.hackerNewsNewestURL, AppConfig.hackerNewsNewestURLBackup,'/newest', page,format) - else: - id = '/newest/' - cachedData = getCache(id,format) - if (cachedData): - return cachedData - else: - hnData = parsePageContent(AppConfig.hackerNewsNewestURL, AppConfig.hackerNewsNewestURLBackup,'/newest', page,format) - if (hnData): - logging.debug('getCache: storing cached value for id %s' % id) - DataCache.putData(id, format,removeNonAscii(hnData), url, referer, remote_addr) - return hnData - else: - logging.warning('getCache: unable to retrieve data for id %s' % id) - return '' - - -#get homepage second page stories -def getHackerNewsSecondPageContent(page='', format='json', url='', referer='', remote_addr=''): - #only cache homepage data - if (page): - return parsePageContent(AppConfig.hackerNewsPage2URL, AppConfig.hackerNewsPage2URLBackup,'/news2', page,format) - else: - id = '/news2' - cachedData = getCache(id,format) - if (cachedData): - return cachedData - else: - hnData = parsePageContent(AppConfig.hackerNewsPage2URL, AppConfig.hackerNewsPage2URLBackup, '/news2', page,format) - if (hnData): - logging.debug('getCache: storing cached value for id %s' % id) - DataCache.putData(id, format,removeNonAscii(hnData), url, referer, remote_addr) - return hnData - else: - logging.warning('getCache: unable to retrieve data for id %s' % id) - return '' - -#get latest homepage stories -def getHackerNewsLatestContent(page='', format='json', url='', referer='', remote_addr='', limit=1): - #only cache homepage data - limit = int(limit) - if (page): - return parsePageContent(AppConfig.hackerNewsURL, AppConfig.hackerNewsURLBackup, '/latest', page,format,limit) - else: - id = '/latest/%s' % limit - cachedData = getCache(id,format) - if (cachedData): - return cachedData - else: - hnData = parsePageContent(AppConfig.hackerNewsURL,AppConfig.hackerNewsURLBackup, '/latest', page,format,limit) - if (hnData): - logging.debug('getCache: storing cached value for id %s' % id) - DataCache.putData(id, format,removeNonAscii(hnData), url, referer, remote_addr) - return hnData - else: - logging.warning('getCache: unable to retrieve data for id %s' % id) - return '' - -#parse HN's homepage -def getHackerNewsPageContent(page='', format='json', url='', referer='', remote_addr=''): - #only cache homepage data - if (page): - return parsePageContent(AppConfig.hackerNewsURL, AppConfig.hackerNewsURLBackup, '/news', page,format) - else: - id = '/news/' - cachedData = getCache(id,format) - if (cachedData): - return cachedData - else: - hnData = parsePageContent(AppConfig.hackerNewsURL, AppConfig.hackerNewsURLBackup, '/news', page,format) - if (hnData): - logging.debug('getCache: storing cached value for id %s' % id) - DataCache.putData(id, format,removeNonAscii(hnData), url, referer, remote_addr) - return hnData - else: - logging.warning('getCache: unable to retrieve data for id %s' % id) - return '' + logging.error('fetchRemoteData: unable to get remote data: %s' % urlStr) + raise Exception("fetchRemoteData: failed") #call remote server to get data. If failed (timeout), try again and again and again and again (4 attempts because urlfetch on the GAE f-ing sucks) def getRemoteData(urlStr, backupUrl): #attempt #1 try: logging.debug('getRemoteData: Attempt #1: %s' % urlStr) - result = urlfetch.fetch(url=urlStr, deadline=30) - if result.status_code == 200: - return result - else: - logging.error('getRemoteData: unable to get remote data...Attempt #1: %s' % urlStr) - raise Exception("getRemoteData: Attempt #1 failed") + return fetchRemoteData(urlStr, 30) except: #attempt #2 try: logging.debug('getRemoteData: First attempt failed... Attempt #2(Backup URL): %s' % backupUrl) - result = urlfetch.fetch(url=backupUrl, deadline=30) - if result.status_code == 200: - return result - else: - logging.error('getRemoteData: unable to get remote data...Attempt #2') - raise Exception("getRemoteData: Attempt #2 failed") + return fetchRemoteData(backupUrl, 30) except: #attempt #3 try: logging.debug('getRemoteData: First attempt failed... Attempt #3: %s' % urlStr) - result = urlfetch.fetch(url=urlStr, deadline=30) - if result.status_code == 200: - return result - else: - logging.error('getRemoteData: unable to get remote data...Attempt #3') - raise Exception("getRemoteData: Attempt #3 failed") + return fetchRemoteData(urlStr, 30) except: #attempt #4 try: logging.debug('getRemoteData: First attempt failed... Attempt #4 (Backup URL): %s' % backupUrl) - result = urlfetch.fetch(url=backupUrl, deadline=30) - if result.status_code == 200: - return result - else: - logging.error('getRemoteData: unable to get remote data...Attempt #4') - return None + return fetchRemoteData(backupUrl, 30) except: logging.error('getRemoteData: unable to get remote data...Attempt #4. Stack') return None - return jsonData + return None #parse content using Beautiful Soup def parsePageContent(hnAPIUrl,hnBackupAPIUrl, apiURL, page='',format='json',limit=0): diff --git a/GetHNAskHandler.py b/GetHNAskHandler.py index 22a1aab..f6920c7 100644 --- a/GetHNAskHandler.py +++ b/GetHNAskHandler.py @@ -16,7 +16,7 @@ import AppConfig import GAHelper from xml.sax.saxutils import escape -import APIUtils +import APIContent import GAHelper from BeautifulSoup import BeautifulSoup @@ -31,11 +31,10 @@ def get(self,format='json',page=''): if ('HTTP_REFERER' in os.environ): referer = os.environ['HTTP_REFERER'] - returnData = MutableString() - returnData = APIUtils.getHackerNewsAskContent(page,format,self.request.url, referer, self.request.remote_addr) + returnData = APIContent.getHackerNewsAskContent(page,format,self.request.url, referer, self.request.remote_addr) if (not returnData or returnData == None or returnData == '' or returnData == 'None'): #call the service again this time without the pageID - returnData = APIUtils.getHackerNewsAskContent('',format,self.request.url, referer, self.request.remote_addr) + returnData = APIContent.getHackerNewsAskContent('',format,self.request.url, referer, self.request.remote_addr) #track this request GAHelper.trackGARequests('/ask', self.request.remote_addr, referer) diff --git a/GetHNBestHandler.py b/GetHNBestHandler.py index 6b9617e..d3d59f0 100644 --- a/GetHNBestHandler.py +++ b/GetHNBestHandler.py @@ -16,7 +16,7 @@ import AppConfig import GAHelper from xml.sax.saxutils import escape -import APIUtils +import APIContent import GAHelper from BeautifulSoup import BeautifulSoup @@ -31,11 +31,10 @@ def get(self,format='json',page=''): if ('HTTP_REFERER' in os.environ): referer = os.environ['HTTP_REFERER'] - returnData = MutableString() - returnData = APIUtils.getHackerNewsBestContent(page,format,self.request.url, referer, self.request.remote_addr) + returnData = APIContent.getHackerNewsBestContent(page,format,self.request.url, referer, self.request.remote_addr) if (not returnData or returnData == None or returnData == '' or returnData == 'None'): #call the service again this time without the pageID - returnData = APIUtils.getHackerNewsBestContent('',format,self.request.url, referer, self.request.remote_addr) + returnData = APIContent.getHackerNewsBestContent('',format,self.request.url, referer, self.request.remote_addr) #track this request GAHelper.trackGARequests('/best', self.request.remote_addr, referer) diff --git a/GetHNCommentsHandler.py b/GetHNCommentsHandler.py index 1c5fc34..90fb316 100644 --- a/GetHNCommentsHandler.py +++ b/GetHNCommentsHandler.py @@ -18,7 +18,7 @@ import AppConfig import GAHelper from xml.sax.saxutils import escape -import APIUtils +import APIContent import GAHelper from BeautifulSoup import BeautifulSoup @@ -33,9 +33,8 @@ def get(self,format,id): if ('HTTP_REFERER' in os.environ): referer = os.environ['HTTP_REFERER'] - returnData = MutableString() - returnData = APIUtils.getHackerNewsComments(id,format,self.request.url, referer, self.request.remote_addr) - + returnData = APIContent.getHackerNewsComments(id,format,self.request.url, referer, self.request.remote_addr) + #track this request GAHelper.trackGARequests('/comments/%s' % (id), self.request.remote_addr, referer) diff --git a/GetHNLatestHandler.py b/GetHNLatestHandler.py index 42a097d..556a6b6 100644 --- a/GetHNLatestHandler.py +++ b/GetHNLatestHandler.py @@ -18,7 +18,7 @@ import AppConfig import GAHelper from xml.sax.saxutils import escape -import APIUtils +import APIContent import GAHelper class HackerNewsLatestPageHandler(webapp.RequestHandler): @@ -32,8 +32,7 @@ def get(self,format='json',limit=1): if ('HTTP_REFERER' in os.environ): referer = os.environ['HTTP_REFERER'] - returnData = MutableString() - returnData = APIUtils.getHackerNewsLatestContent('',format,self.request.url, referer, self.request.remote_addr, limit) + returnData = APIContent.getHackerNewsLatestContent('',format,self.request.url, referer, self.request.remote_addr, limit) #track this request GAHelper.trackGARequests('/latest', self.request.remote_addr, referer) diff --git a/GetHNNestedCommentsHandler.py b/GetHNNestedCommentsHandler.py index d5f3478..22098c7 100644 --- a/GetHNNestedCommentsHandler.py +++ b/GetHNNestedCommentsHandler.py @@ -17,7 +17,7 @@ import AppConfig import GAHelper from xml.sax.saxutils import escape -import APIUtils +import APIContent import GAHelper from BeautifulSoup import BeautifulSoup @@ -32,8 +32,7 @@ def get(self,format,id): if ('HTTP_REFERER' in os.environ): referer = os.environ['HTTP_REFERER'] - returnData = MutableString() - returnData = APIUtils.getHackerNewsNestedComments(id,format,self.request.url, referer, self.request.remote_addr) + returnData = APIContent.getHackerNewsNestedComments(id,format,self.request.url, referer, self.request.remote_addr) #track this request GAHelper.trackGARequests('/nestedcomments/%s' % (id), self.request.remote_addr, referer) diff --git a/GetHNNewestHandler.py b/GetHNNewestHandler.py index 625dd3a..6f1a3f3 100644 --- a/GetHNNewestHandler.py +++ b/GetHNNewestHandler.py @@ -16,7 +16,7 @@ import AppConfig import GAHelper from xml.sax.saxutils import escape -import APIUtils +import APIContent import GAHelper from BeautifulSoup import BeautifulSoup @@ -31,11 +31,10 @@ def get(self,format='json',page=''): if ('HTTP_REFERER' in os.environ): referer = os.environ['HTTP_REFERER'] - returnData = MutableString() - returnData = APIUtils.getHackerNewsNewestContent(page,format,self.request.url, referer, self.request.remote_addr) + returnData = APIContent.getHackerNewsNewestContent(page,format,self.request.url, referer, self.request.remote_addr) if (not returnData or returnData == None or returnData == '' or returnData == 'None'): #call the service again this time without the pageID - returnData = APIUtils.getHackerNewsNewestContent('',format,self.request.url, referer, self.request.remote_addr) + returnData = APIContent.getHackerNewsNewestContent('',format,self.request.url, referer, self.request.remote_addr) #track this request GAHelper.trackGARequests('/newest', self.request.remote_addr, referer) diff --git a/GetHNPageContentHandler.py b/GetHNPageContentHandler.py index 80cf643..4191d61 100644 --- a/GetHNPageContentHandler.py +++ b/GetHNPageContentHandler.py @@ -18,7 +18,7 @@ import AppConfig import GAHelper from xml.sax.saxutils import escape -import APIUtils +import APIContent import GAHelper class HackerNewsPageHandler(webapp.RequestHandler): @@ -32,11 +32,10 @@ def get(self,format='json',page=''): if ('HTTP_REFERER' in os.environ): referer = os.environ['HTTP_REFERER'] - returnData = MutableString() - returnData = APIUtils.getHackerNewsPageContent(page,format,self.request.url, referer, self.request.remote_addr) + returnData = APIContent.getHackerNewsPageContent(page,format,self.request.url, referer, self.request.remote_addr) if (not returnData or returnData == None or returnData == '' or returnData == 'None'): #call the service again this time without the pageID - returnData = APIUtils.getHackerNewsPageContent('',format,self.request.url, referer, self.request.remote_addr) + returnData = APIContent.getHackerNewsPageContent('',format,self.request.url, referer, self.request.remote_addr) #track this request GAHelper.trackGARequests('/news', self.request.remote_addr, referer) diff --git a/GetHNRSSHandler.py b/GetHNRSSHandler.py index 8da06a9..0378eca 100644 --- a/GetHNRSSHandler.py +++ b/GetHNRSSHandler.py @@ -17,7 +17,7 @@ import Formatter import GAHelper from xml.sax.saxutils import escape -import APIUtils +import APIContent class HackerNewsRSSHandler(webapp.RequestHandler): #controller main entry @@ -26,7 +26,7 @@ def get(self,format='json'): self.response.headers['Content-Type'] = Formatter.contentType(format) returnData = MutableString() - returnData = APIUtils.getHackerNewsRSS(format) + returnData = APIContent.getHackerNewsRSS(format) referer = '' if ('HTTP_REFERER' in os.environ): diff --git a/GetHNSecondPageHandler.py b/GetHNSecondPageHandler.py index e3eee6b..fa20904 100644 --- a/GetHNSecondPageHandler.py +++ b/GetHNSecondPageHandler.py @@ -18,7 +18,7 @@ import AppConfig import GAHelper from xml.sax.saxutils import escape -import APIUtils +import APIContent import GAHelper from google.appengine.api import urlfetch @@ -33,11 +33,10 @@ def get(self,format='json',page=''): if ('HTTP_REFERER' in os.environ): referer = os.environ['HTTP_REFERER'] - returnData = MutableString() - returnData = APIUtils.getHackerNewsSecondPageContent(page,format,self.request.url, referer, self.request.remote_addr) + returnData = APIContent.getHackerNewsSecondPageContent(page,format,self.request.url, referer, self.request.remote_addr) if (not returnData or returnData == None or returnData == '' or returnData == 'None'): #call the service again - returnData = APIUtils.getHackerNewsSecondPageContent(page,format,self.request.url, referer, self.request.remote_addr) + returnData = APIContent.getHackerNewsSecondPageContent(page,format,self.request.url, referer, self.request.remote_addr) #track this request GAHelper.trackGARequests('/news2', self.request.remote_addr, referer) diff --git a/GetHNSubmittedHandler.py b/GetHNSubmittedHandler.py index d2399a6..f03d201 100644 --- a/GetHNSubmittedHandler.py +++ b/GetHNSubmittedHandler.py @@ -18,7 +18,7 @@ import AppConfig import GAHelper from xml.sax.saxutils import escape -import APIUtils +import APIContent import GAHelper class HackerNewsSubmittedHandler(webapp.RequestHandler): @@ -31,8 +31,7 @@ def get(self,format,user): if ('HTTP_REFERER' in os.environ): referer = os.environ['HTTP_REFERER'] - returnData = MutableString() - returnData = APIUtils.getHackerNewsSubmittedContent(user,format,self.request.url, referer, self.request.remote_addr) + returnData = APIContent.getHackerNewsSubmittedContent(user,format,self.request.url, referer, self.request.remote_addr) #track this request GAHelper.trackGARequests('/submitted/%s' % (user), self.request.remote_addr, referer) diff --git a/app.yaml b/app.yaml index 116b92d..7e93231 100644 --- a/app.yaml +++ b/app.yaml @@ -1,5 +1,5 @@ application: hndroidapi -version: 1 +version: 2 runtime: python api_version: 1 diff --git a/main.py b/main.py index 059738f..2b4afbd 100644 --- a/main.py +++ b/main.py @@ -21,7 +21,7 @@ class MainHandler(webapp.RequestHandler): def get(self): - template_values = {'last_updated': '10/22/11'} + template_values = {'last_updated': '10/23/11'} path = os.path.join(os.path.dirname(__file__), 'templates') path = os.path.join(path, 'index.html') self.response.out.write(template.render(path, template_values)) diff --git a/templates/index.html b/templates/index.html index 5c46321..35e43c0 100644 --- a/templates/index.html +++ b/templates/index.html @@ -35,7 +35,7 @@ function getLatestAdvanced() { - window.location = apiURL + '/latest/format/' + format('latest-format') + '/limit/' + elm('latest-limit') + window.location = apiURL + '/latest/format/' + format('latest-format') + '/limit/' + elm('latest-limit'); } function getBestSimple() @@ -50,32 +50,37 @@ function getNewsAdvanced() { - window.location = apiURL + '/news/format/' + format('newsadvanced-format') + '/page/' + elm('newsadvanced-pageid') + window.location = apiURL + '/news/format/' + format('newsadvanced-format') + '/page/' + elm('newsadvanced-pageid'); } function getNewestAdvanced() { - window.location = apiURL + '/newest/format/' + format('newestadvanced-format') + '/page/' + elm('newestadvanced-pageid') + window.location = apiURL + '/newest/format/' + format('newestadvanced-format') + '/page/' + elm('newestadvanced-pageid'); } function getAskAdvanced() { - window.location = apiURL + '/ask/format/' + format('ask-format') + '/page/' + elm('ask-pageid') + window.location = apiURL + '/ask/format/' + format('ask-format') + '/page/' + elm('ask-pageid'); } function getBestAdvanced() { - window.location = apiURL + '/best/format/' + format('best-format') + '/page/' + elm('best-pageid') + window.location = apiURL + '/best/format/' + format('best-format') + '/page/' + elm('best-pageid'); } function getByUser() { - window.location = apiURL + '/submitted/format/' + format('user-format') + '/user/' + elm('user-pageid') + window.location = apiURL + '/submitted/format/' + format('user-format') + '/user/' + elm('user-pageid'); } function getComments() { - window.location = apiURL + '/comments/format/' + format('comments-format') + '/id/' + elm('comments-pageid') + window.location = apiURL + '/comments/format/' + format('comments-format') + '/id/' + elm('comments-pageid'); + } + + function getNestedComments() + { + window.location = apiURL + '/nestedcomments/format/' + format('nestedcomments-format') + '/id/' + elm('nestedcomments-pageid'); } function elm(id) { return (document.getElementById(id)) ? document.getElementById(id).value : '' } @@ -101,7 +106,9 @@

Hacker News API (unofficial)

Last updated: {{last_updated}}

- Update (10/22/11): Thanks to my friend Eric I now have a dedicated server to handle API traffic. The API has been down for a few hours on Friday, October 21st because of GAE urlfetch (DownloadError: ApplicationError: 2) errors. Apparently, GAE started to deny requests to HN so the data had to be retrieved from another server. The API still runs on GAE but the data is being fetched from another server and then provided to GAE instances for processing. + Update (10/23/11): The API now supports nested comments as a new API call (/nestedcomments) (thanks to Suan Aik Yeo). The old comments API (/comments) has been deprecated. +
+ Update (10/22/11): Thanks to my friend Eric I now have a dedicated server to handle API traffic. The API has been down for a few hours on Friday, October 21st because of GAE urlfetch (DownloadError: ApplicationError: 2) errors. Apparently, GAE started to deny requests to HN so the data had to be retrieved from another server. The API still runs on GAE but the data is being fetched from another server and then provided to GAE instances for processing.

The API is currently in beta and was developed for the Hacker News Droid app. The API is built in Python and hosted on AppEngine. I used the Beautiful Soup library for HTML parsing/scraping and an external dedicated box (generously provided by ) to retrieve content from HN so it can be processed by GAE instances and served up.
@@ -118,12 +125,13 @@

Last updated: {{last_updated}}


@@ -181,6 +189,56 @@

Homepage: Advanced API


+
+ +

Comments by Story ID (Deprecated as of 10/23/11)

+
+ Returns story comments by id  (returns XML or JSON)

+ + + + + + + + + + + + + + + + +
URL:http://hndroidapi.appspot.com/comments/format/<format>/id/<id>
Story Id: (e.g. 3423232)
Format:JSON XML
+
+
+ +
+ +

Nested Comments by Story ID

+
+ Returns nested story comments by id  (returns XML or JSON)

+ + + + + + + + + + + + + + + + +
URL:http://hndroidapi.appspot.com/nestedcomments/format/<format>/id/<id>
Story Id: (e.g. 3423232)
Format:JSON XML
+
+
+

Latest: Simple API

@@ -383,36 +441,13 @@

Submissions by User


-
- -

Comments by Story ID

-
- Returns story comments by id  (returns XML or JSON)

- - - - - - - - - - - - - - - - -
URL:http://hndroidapi.appspot.com/comments/format/<format>/id/<id>
Story Id: (e.g. 3423232)
Format:JSON XML
-
-
+

- Built by Gleb Popov. Last updated: {{last_updated}}. + Author: Gleb Popov. Credits: Suan Aik Yeo (Nested Comments API), Eric Kigathi (backend server support). Last updated: {{last_updated}}.