db.py 9.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256
  1. from pymongo import MongoClient, errors, DESCENDING, ASCENDING
  2. from bson.objectid import ObjectId
  3. from datetime import timedelta
  4. import datetime
  5. import re
  6. import news
  7. import pytz
  8. import time
  9. class db():
  10. DB = "alexis"
  11. def __init__(self):
  12. self.client = MongoClient('localhost', 27017)
  13. self.c_sources = self.client[self.DB]["sources"]
  14. self.c_articles = self.client[self.DB]["articles"]
  15. def change_timezone(self, el):
  16. #gmt = pytz.timezone("America/Buenos_Aires") # FIXME
  17. local = pytz.timezone("US/Central")
  18. est = pytz.timezone('US/Eastern')
  19. fmt = '%Y-%m-%d %H:%M:%S %Z%z'
  20. el["scrap_date"] = local.localize(el["scrap_date"])
  21. el["scrap_date"] = el["scrap_date"].astimezone(est)
  22. el["scrap_date"] = el["scrap_date"].strftime(fmt)
  23. return el
  24. def jsonable(self, el):
  25. if el is None or "_id" not in el:
  26. return el
  27. el["_id"] = str(el["_id"])
  28. return el
  29. def filter_articles(self, filter, limit=80):
  30. start = time.time()
  31. db_filter = {}
  32. db_filter["matches"] = True
  33. db_filter["deleted"] = False
  34. db_filter["scrap_date"] = {}
  35. if "maxdate" in filter and len(filter["maxdate"]) > 7:
  36. db_filter["scrap_date"]["$lt"] = datetime.datetime.strptime(
  37. filter["maxdate"], "%Y-%m-%d")
  38. if "mindate" in filter and len(filter["mindate"]) > 7:
  39. db_filter["scrap_date"]["$gt"] = datetime.datetime.strptime(
  40. filter["mindate"], "%Y-%m-%d")
  41. if db_filter["scrap_date"] == {}:
  42. del db_filter["scrap_date"]
  43. if "title" in filter and filter["title"] != "":
  44. filter["title"] = filter["title"].replace(
  45. "(", "\(").replace(")", "\)")
  46. db_filter["title"] = {"$regex": ".*%s.*" %
  47. filter["title"], "$options": 'i'}
  48. if "site" in filter and filter["site"] != "":
  49. filter["site"] = filter["site"].replace(
  50. "(", "\(").replace(")", "\)")
  51. db_filter["source_name"] = {
  52. "$regex": ".*%s.*" % filter["site"], "$options": 'i'}
  53. # db_filter["$or"]=[
  54. # {"source_name": {"$regex":".*%s.*"%filter["site"], "$options": 'i'}},
  55. # {"url": {"$regex":".*%s.*"%filter["site"], "$options": 'i' }}
  56. # ]
  57. if "minweight" not in filter or not filter["minweight"].isdigit():
  58. filter["minweight"] = 1
  59. if "category" not in filter or filter["category"] == "-1":
  60. filter["category"] = -1
  61. if int(filter["category"]) >= 0:
  62. db_filter["category"] = int(filter["category"])
  63. # pass
  64. db_filter["weight"] = {"$gt": int(filter["minweight"])}
  65. if "keyword" in filter and len(filter["keyword"]) > 0:
  66. db_filter["kw"] = {"$all": filter["keyword"]}
  67. # db_filter["kw.0"]={"$exists":False}
  68. pageNumber = 0
  69. if "page" in filter:
  70. pageNumber = filter["page"]
  71. # print(db_filter)
  72. print(db_filter)
  73. fields = {"html": 0, "text": 0, "authors":0,"lowerurl":0}
  74. fields = {"kw":1,"scrap_date":1, "source_name":1,"summary":1,"title":1,"url":1,"weight":1}
  75. res = self.c_articles.find(db_filter, fields)
  76. if res is not None:
  77. toSkip = (((pageNumber - 1) * limit) if pageNumber > 0 else 0)
  78. #q = res.skip(toSkip).sort("scrap_date", DESCENDING).limit(limit)
  79. q = res.skip(toSkip).sort("scrap_date", DESCENDING).limit(limit*3) #+2 pages
  80. start=time.time()
  81. else:
  82. q = []
  83. count = 0
  84. ret_arts = [self.jsonable(self.change_timezone(p)) for p in q]
  85. count = len(ret_arts)+toSkip
  86. ret_arts = ret_arts[:limit]
  87. #count = len(ret_arts)
  88. print("pre-ret", time.time()-start)
  89. return {'articles': ret_arts,
  90. 'count': count}
  91. def article(self, id):
  92. ret = self.c_articles.find_one({"_id": ObjectId(id)})
  93. return self.jsonable(ret)
  94. def delete_article(self, artid):
  95. return self.c_articles.update({"_id": ObjectId(artid)}, {"$set": {"deleted": True}})
  96. def undelete_article(self, artid):
  97. return self.c_articles.update({"_id": ObjectId(artid)}, {"$set": {"deleted": False}})
  98. def sources(self, id=None):
  99. if id is None:
  100. return [self.jsonable(p) for p in self.c_sources.find()]
  101. ret = self.c_sources.find_one({"_id": ObjectId(id)})
  102. return self.jsonable(ret)
  103. def rss_sources(self):
  104. return [p for p in self.c_sources.find({"rss": 1})]
  105. def insert_article(self, o):
  106. try:
  107. self.c_articles.insert_one(o)
  108. return True
  109. except errors.DuplicateKeyError:
  110. print("Dup")
  111. return False
  112. def count_error(self):
  113. return self.c_articles.count({'error': True})
  114. def purge_error(self):
  115. self.c_articles.remove({'error': True})
  116. def article_exists(self, url, src):
  117. #slow as balls
  118. #ret = self.c_articles.find({'source': src, 'lowerurl': url.lower()},{"_id":1}).limit(1) #faster than findone
  119. #articles may have different src and same url.
  120. ret = self.c_articles.find({'lowerurl': url.lower()},{"_id":1}).limit(1) #faster than findone
  121. c = len([ 1 for r in ret ] )
  122. #findOne returns the elem. find returns the cursor (pointer)
  123. return c>0
  124. def lower_arr(self, arr):
  125. return [a.lower() for a in arr]
  126. def to_regex(self, arr): # "ACH" | "PAYMENT" | "EBAY"
  127. return [re.compile('\\b' + w.lower() + '\\b') for w in arr]
  128. def get_keywords_list(self):
  129. kw = self.client[self.DB]["keywords"]
  130. return [k["keyword"] for k in kw.find({})]
  131. def get_keywords(self, category):
  132. kw = self.client[self.DB]["keywords"]
  133. ret = [{"weight": k["weight"],
  134. "kw": k["keyword"],
  135. "pattern": self.to_regex(k["keyword"].split("|"))} for k in kw.find({})]
  136. # FIXME, categories
  137. return ret
  138. def add_keyword(self, kw):
  139. if 'keyword' not in kw or 'weight' not in kw:
  140. return False
  141. kw["pattern"] = self.to_regex(kw["keyword"].split("|"))
  142. kwdb = self.client[self.DB]["keywords"]
  143. try:
  144. kwdb.insert_one(kw)
  145. return True
  146. except errors.DuplicateKeyError:
  147. print("Dup KW")
  148. return False
  149. def update_keyword(self, kw):
  150. if 'keyword' not in kw or 'weight' not in kw:
  151. return False
  152. kw["pattern"] = self.to_regex(kw["keyword"].split("|"))
  153. kwdb = self.client[self.DB]["keywords"]
  154. try:
  155. kwdb.update({'keyword': kw['keyword']}, {'$set': kw})
  156. return True
  157. except Exception:
  158. print("Dup KW")
  159. return False
  160. def delete_keyword(self, kw):
  161. kwdb = self.client[self.DB]["keywords"]
  162. try:
  163. kwdb.remove({'keyword': kw})
  164. return True
  165. except Exception:
  166. return False
  167. def add_source(self, url, rss, selector, name, category):
  168. self.c_sources.insert({"link": url,
  169. "rss": rss,
  170. "selector": selector,
  171. "name": name,
  172. "category": category})
  173. def reparse_articles(self, articles, kw):
  174. for a in articles:
  175. # print(a)
  176. art = self.c_articles.find_one({"url": a["url"]})
  177. res = news.match_keywords(kw, art["text"])
  178. #(matches,w_sum,matching)
  179. # if(res["matches"]):
  180. print(res)
  181. self.c_articles.update({"_id": ObjectId(a["_id"])}, {"$set": res})
  182. #matches, kw, weight
  183. def get_sources_with_error(self):
  184. r = self.c_articles.aggregate([{'$match': {'error': True}},
  185. {'$group': {'_id': '$source_name', 'count': {'$sum': 1}}}])
  186. return r
  187. def get_sources_with_count(self):
  188. # TODO filter with scrap_date, this year mb?
  189. r = self.c_articles.aggregate([{'$match': {'matches': True}},
  190. {'$group':
  191. {'_id': '$source_name',
  192. 'count': {'$sum': 1},
  193. 'weight': {'$avg': '$weight'}
  194. }
  195. }])
  196. # t = self.c_articles.aggregate([{'$group': {'_id': '$source_name', 'count': {'$sum': 1}}}])
  197. return r
  198. def get_keywords_with_count(self):
  199. this_year = datetime.datetime.now() - timedelta(days=180)
  200. r = self.c_articles.aggregate([{'$match': {
  201. '$and': [{'matches': True}, {'scrap_date': {'$gt': this_year}}]
  202. }},
  203. {'$unwind': '$kw'},
  204. {'$group':
  205. {'_id': '$kw', 'sum': {'$sum': 1}}
  206. }
  207. ])
  208. d1 = {}
  209. for res in r:
  210. d1[res['_id']] = res['sum']
  211. return d1