db.py 9.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263
  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. ret = sorted(kw.find({}, {'_id': False}), key=lambda k: k['keyword'].lower())
  135. return list(ret)
  136. def get_keywords(self, category):
  137. kw = self.c_kw
  138. ret = [{"weight": k["weight"],
  139. "kw": k["keyword"],
  140. "pattern": self.to_regex(k["keyword"].split("|"))} for k in kw.find({})]
  141. # FIXME, categories
  142. return ret
  143. def add_keyword(self, kw, weight):
  144. kwdb = self.c_kw
  145. try:
  146. data = {'keyword': kw, 'weight': weight}
  147. kwdb.insert_one(data)
  148. return True
  149. except errors.DuplicateKeyError:
  150. print("Dup KW")
  151. return False
  152. def update_keyword(self, kw):
  153. if 'oldkw' not in kw or 'weight' not in kw or 'newkw' not in kw:
  154. return False
  155. if not isinstance(kw['weight'], int):
  156. return False
  157. kwdb = self.c_kw
  158. try:
  159. kwdb.update({'keyword': kw['oldkw']},
  160. {'$set':
  161. {'weight': kw['weight'], 'keyword': kw['newkw']}})
  162. return True
  163. except Exception:
  164. print("Dup KW")
  165. return False
  166. def delete_keyword(self, kw):
  167. kwdb = self.c_kw
  168. try:
  169. kwdb.remove({'keyword': kw})
  170. return True
  171. except Exception:
  172. return False
  173. def add_source(self, url, rss, selector, name, category):
  174. self.c_sources.insert({"link": url,
  175. "rss": rss,
  176. "selector": selector,
  177. "name": name,
  178. "category": category})
  179. def reparse_articles(self, articles, kw):
  180. for a in articles:
  181. # print(a)
  182. art = self.c_articles.find_one({"url": a["url"]})
  183. res = news.match_keywords(kw, art["text"])
  184. #(matches,w_sum,matching)
  185. # if(res["matches"]):
  186. print(res)
  187. self.c_articles.update({"_id": ObjectId(a["_id"])}, {"$set": res})
  188. #matches, kw, weight
  189. def get_sources_with_error(self):
  190. r = self.c_articles.aggregate([{'$match': {'error': True}},
  191. {'$group': {'_id': '$source_name', 'count': {'$sum': 1}}}])
  192. return r
  193. def get_sources_with_count(self):
  194. # TODO filter with scrap_date, this year mb?
  195. r = self.c_articles.aggregate([{'$match': {'matches': True}},
  196. {'$group':
  197. {'_id': '$source_name',
  198. 'count': {'$sum': 1},
  199. 'weight': {'$avg': '$weight'}
  200. }
  201. }])
  202. # t = self.c_articles.aggregate([{'$group': {'_id': '$source_name', 'count': {'$sum': 1}}}])
  203. return r
  204. def get_keywords_with_count(self):
  205. this_year = datetime.datetime.now() - timedelta(days=180)
  206. r = self.c_articles.aggregate([{'$match': {
  207. '$and': [{'matches': True}, {'scrap_date': {'$gt': this_year}}]
  208. }},
  209. {'$unwind': '$kw'},
  210. {'$group':
  211. {'_id': '$kw', 'sum': {'$sum': 1}}
  212. }
  213. ])
  214. d1 = {}
  215. for res in r:
  216. d1[res['_id']] = res['sum']
  217. return d1