crauler.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323
  1. # -*- encoding: utf-8 -*-
  2. #
  3. """
  4. virtual_env = os.path.expanduser('~/projects/world-it-planet/env')
  5. activate_this = os.path.join(virtual_env, 'bin/activate_this.py')
  6. exec(open(activate_this).read(), dict(__file__=activate_this))
  7. """
  8. import os, traceback
  9. from bs4 import BeautifulSoup
  10. import random, time, datetime
  11. import requests
  12. from requests.exceptions import ProxyError
  13. from urllib.parse import urlparse, urljoin
  14. import tldextract
  15. #декларативное определение
  16. from sqlalchemy import Column, Integer, String, Text, create_engine
  17. from sqlalchemy.ext.declarative import declarative_base
  18. from sqlalchemy.orm import sessionmaker
  19. from sqlalchemy.sql.expression import func
  20. #---------------------------------- Variables ----------
  21. donors = [
  22. "http://npedkol.ru/",
  23. "https://it-events.com/",
  24. "https://habr.com/ru/all/",
  25. "https://innopolis.university/",
  26. ]
  27. keywords = [
  28. "олимпиада",
  29. "хакатон",
  30. "конкурс"
  31. ]
  32. user_agents = [
  33. "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.9; rv:45.0) Gecko/20100101 Firefox/45.0",
  34. "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:86.0) Gecko/20100101 Firefox/86.0",
  35. "Mozilla/5.0 (Windows NT 5.1; rv:23.0) Gecko/20100101 Firefox/23.0",
  36. "Mozilla/5.0 (Windows NT 5.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/29.0.1547.62 Safari/537.36",
  37. "Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.1; WOW64; Trident/6.0)",
  38. "Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.1; Trident/6.0)",
  39. "Mozilla/5.0 (Windows NT 6.1; rv:23.0) Gecko/20100101 Firefox/23.0",
  40. "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/29.0.1547.62 Safari/537.36",
  41. "Opera/9.80 (Windows NT 5.1) Presto/2.12.388 Version/12.16",
  42. "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/29.0.1547.62 Safari/537.36",
  43. "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.22 (KHTML, like Gecko) Chrome/25.0.1364.172 YaBrowser/1.7.1364.21027 Safari/537.22",
  44. "Opera/9.80 (Windows NT 6.1; WOW64) Presto/2.12.388 Version/12.16",
  45. "Mozilla/5.0 (iPad; CPU OS 6_1_3 like Mac OS X) AppleWebKit/536.26 (KHTML, like Gecko) Version/6.0 Mobile/10B329 Safari/8536.25",
  46. "Opera/9.80 (Windows NT 6.1; WOW64) Presto/2.12.388 Version/12.15",
  47. "Mozilla / 5.0 (Macintosh; Intel Mac OS X 10.14; rv: 75.0) Gecko / 20100101 Firefox / 75.0",
  48. "Mozilla / 5.0 (Windows NT 6.1; Win64; x64; rv: 74.0) Gecko / 20100101 Firefox / 74.0",
  49. "Mozilla / 5.0 (Macintosh; Intel Mac OS X 10_15_3) AppleWebKit / 537.36 (KHTML, как Gecko) Chrome / 80.0.3987.163 Safari / 537.36",
  50. "Dalvik/2.1.0 (Linux; U; Android 10; Mi 9T MIUI/V12.0.5.0.QFJMIXM)"
  51. ]
  52. bad_file_extensions = ["pdf", "doc", "docx"]
  53. #---------------------------------- Variables End ----------
  54. out_s = ""
  55. #Инициализация SQLLite
  56. basedir = os.path.abspath(os.path.dirname(__file__))
  57. SQLALCHEMY_DATABASE_URI = 'sqlite:///' + os.path.join(basedir, 'all.db')
  58. engine = create_engine(SQLALCHEMY_DATABASE_URI, pool_pre_ping=True)
  59. Base = declarative_base()
  60. # класс БД собранной информации
  61. class Links(Base):
  62. __tablename__ = 'links'
  63. id = Column(Integer, primary_key=True, autoincrement=True)
  64. donor = Column(String(255))
  65. title = Column(String(512))
  66. href = Column(String(512))
  67. parse_date = Column(Integer)
  68. html = Column(Text)
  69. text = Column(Text)
  70. lemmas = Column(Text) # набор лемм из текста (мешок слов)
  71. level = Column(Integer) # уровень вложенности ссылки от корня сайта
  72. status = Column(Integer) # 0 - не загружена, 1 - загружена, ключевика нет; 2 - ключевик есть, уведомление не отправлено; 3 - уведомление не отправлено
  73. def __init__(self, donor, title, href, parse_date, html, text, lemmas, level, status):
  74. self.donor = donor
  75. self.title = title
  76. self.href = href
  77. self.parse_date = parse_date
  78. self.html = html
  79. self.text = text
  80. self.lemmas = lemmas
  81. self.level = level
  82. self.status = status
  83. def __repr__(self):
  84. return "<Link('%s', '%s')>" % (self.title, self.href)
  85. class Log(Base):
  86. __tablename__ = 'log'
  87. id = Column(Integer, primary_key=True, autoincrement=True)
  88. action = Column(String(64))
  89. status = Column(String(64))
  90. time = Column(Integer)
  91. donor = Column(String(64))
  92. def __init__(self, action, status, time, donor):
  93. self.action = action
  94. self.status = status
  95. self.time = time
  96. self.donor = donor
  97. def __repr__(self):
  98. return "<Log('%s','%s', '%s')>" % (self.action, self.status)
  99. # Создание таблицы
  100. Base.metadata.create_all(engine)
  101. Session = sessionmaker(bind=engine)
  102. sqllite_session = Session()
  103. def select_good_link(link_href, donor):
  104. is_bad = False
  105. donor_parsed = urlparse(donor)
  106. link_parsed = urlparse(link_href)
  107. # приводим ссылку к каноничному виду
  108. if (link_parsed.hostname == None):
  109. link_2 = donor_parsed.scheme + "://" + donor_parsed.hostname
  110. link_2 = urljoin(link_2, link_href)
  111. else:
  112. link_2 = link_href
  113. # нас интересуют только внутренние ссылки (и с поддоменов тоже)
  114. donor_domain = tldextract.extract(donor).domain
  115. link_domain = tldextract.extract(link_2).domain
  116. print("link_domain: ", link_domain)
  117. if (link_domain != donor_domain):
  118. print("Внешняя ссылка, пропускаем")
  119. is_bad = True
  120. # убираем .pdf, mailto и пр.
  121. pos = str(link_2).find("mailto")
  122. if (pos != -1):
  123. print("mailto, пропускаем")
  124. is_bad = True
  125. # GET переменные не нужны
  126. try:
  127. link_2, get = str(link_2).split("?")
  128. except:
  129. pass
  130. filename, file_extension = os.path.splitext(link_2)
  131. if (file_extension in bad_file_extensions):
  132. print(file_extension, ", пропускаем")
  133. is_bad = True
  134. # сам домен тоже не нужен
  135. if (link_2 == donor):
  136. print("Главная страница, пропускаем")
  137. is_bad = True
  138. return is_bad, link_2
  139. new_links = 0
  140. for donor in donors:
  141. print("Парсим ", donor)
  142. #формируем запрос
  143. user_agent = random.choice(user_agents)
  144. donor_parsed = urlparse(donor)
  145. headers = {
  146. "Host": str(donor_parsed.hostname),
  147. 'User-Agent': str(user_agent),
  148. 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8',
  149. 'Accept-Language': 'ru-RU,ru;q=0.8,en-US;q=0.5,en;q=0.3',
  150. 'Accept-Encoding': 'gzip, deflate, br',
  151. 'Referer': str(donor),
  152. 'Upgrade-Insecure-Requests': '1',
  153. 'Connection': 'keep-alive'}
  154. response = requests.get(donor, headers=headers)
  155. post_html = response.text
  156. with open(os.path.join(basedir, donor_parsed.hostname+'.html'), 'w', encoding="utf-8") as f:
  157. f.write(post_html)
  158. # Парсим ссылки
  159. soup = BeautifulSoup(post_html, "lxml")
  160. all_links = soup.find_all('a')
  161. for link in all_links:
  162. link_href = link.get('href')
  163. print(link_href)
  164. is_bad, link_1 = select_good_link(link_href, donor)
  165. if (is_bad):
  166. print()
  167. continue
  168. print("link_1: ", link_1)
  169. #уникальность ссылки
  170. print("Проверяем уникальность")
  171. link_n = sqllite_session.query(Links).filter(Links.href == link_1).count()
  172. print("link_n: " + str(link_n))
  173. if (link_n == 0):
  174. print("Добавляем в базу")
  175. new_link = Links(
  176. title = "",
  177. href = link_1,
  178. donor = donor,
  179. parse_date = int(time.time()),
  180. html = "",
  181. text = "",
  182. lemmas = "",
  183. level = 1,
  184. status = 0
  185. )
  186. sqllite_session.add(new_link)
  187. sqllite_session.commit()
  188. new_links += 1
  189. else:
  190. print("В базе ссылка есть")
  191. print ()
  192. """
  193. new_log = Log(
  194. action = "parse",
  195. status = status,
  196. time = int(time.time()),
  197. donor = 'habr.ru',
  198. )
  199. sqllite_session.add(new_log)
  200. """
  201. print("Новых ссылок 1-го уровня: ", new_links)
  202. new_links = 0
  203. # парсим по 10 случайных ссылок за раз
  204. for i in range(0, 10):
  205. # случайная ссылка 1-го и выше уровня
  206. link = sqllite_session.query(Links).filter(Links.level == 1).order_by(func.random()).first()
  207. donor = link.href
  208. print("Парсим ", link.href)
  209. #формируем запрос
  210. user_agent = random.choice(user_agents)
  211. donor_parsed = urlparse(donor)
  212. headers = {
  213. "Host": str(donor_parsed.hostname),
  214. 'User-Agent': str(user_agent),
  215. 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8',
  216. 'Accept-Language': 'ru-RU,ru;q=0.8,en-US;q=0.5,en;q=0.3',
  217. 'Accept-Encoding': 'gzip, deflate, br',
  218. 'Referer': str(donor),
  219. 'Upgrade-Insecure-Requests': '1',
  220. 'Connection': 'keep-alive'}
  221. try:
  222. response = requests.get(donor, headers=headers)
  223. post_html = response.text
  224. with open(os.path.join(basedir, donor_parsed.hostname+'.html'), 'w', encoding="utf-8") as f:
  225. f.write(post_html)
  226. # Парсим ссылки
  227. soup = BeautifulSoup(post_html, "lxml")
  228. all_links = soup.find_all('a')
  229. for link in all_links:
  230. link_href = link.get('href')
  231. print(link_href)
  232. is_bad, link_2 = select_good_link(link_href, donor)
  233. if (is_bad):
  234. print()
  235. continue
  236. print("link_3: ", link_2)
  237. #уникальность ссылки
  238. print("Проверяем уникальность")
  239. link_n = sqllite_session.query(Links).filter(Links.href == link_2).count()
  240. print("link_n: " + str(link_n))
  241. if (link_n == 0):
  242. print("Добавляем в базу")
  243. new_link = Links(
  244. title = "",
  245. href = link_2,
  246. donor = donor,
  247. parse_date = int(time.time()),
  248. html = "",
  249. text = "",
  250. lemmas = "",
  251. level = 2,
  252. status = 0
  253. )
  254. sqllite_session.add(new_link)
  255. sqllite_session.commit()
  256. new_links += 1
  257. else:
  258. print("В базе ссылка есть")
  259. except:
  260. status = str(traceback.format_exc())
  261. print("Ошибка: ", str(status))
  262. print ()
  263. print("Новых ссылок 2-го уровня: ", new_links)