db.py 9.3 KB

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