Advertisement
Guest User

Untitled

a guest
Feb 20th, 2019
247
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 13.44 KB | None | 0 0
  1. #!/usr/bin/python3
  2. # -*- Coding: utf-8 -*-
  3.  
  4. import threading
  5. import requests
  6. import random
  7. from bs4 import BeautifulSoup
  8. import pika
  9. import time
  10. import json
  11. import os
  12. from ParserModels.AvitoSubjectModel import AvitoSubject
  13. from ParserModels.ProxyModel import Proxy
  14. from ParserModels.CategoryModel import Category
  15. from ParserModels.CityModel import City
  16.  
  17. os.environ['TZ'] = 'Europe/Samara'
  18. time.tzset()
  19.  
  20. class AvitoDaemon(object):
  21.     connection = None
  22.     pages_channel = None
  23.  
  24.     def __init__(self):
  25.         """
  26.        Инициализируем соединение с RabbitMQ
  27.        """
  28.  
  29.         # подключаем конфиг
  30.         config_filename = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'config', 'rabbit.json')
  31.         rabbit_config = json.load(open( config_filename, 'r' ))
  32.  
  33.         # задаём параметры подключения
  34.         credentials = pika.PlainCredentials(rabbit_config['user'], rabbit_config['password'])
  35.         parameters = pika.ConnectionParameters(rabbit_config['host'], rabbit_config['port'], '/', credentials, socket_timeout=10)
  36.         self.connection = pika.BlockingConnection(parameters)
  37.  
  38.         # инициализация канала и очереди для URLов страниц
  39.         self.pages_channel = self.connection.channel()
  40.         # кол-во единовременно получаемых сообщений
  41.         self.pages_channel.basic_qos(prefetch_count=1)
  42.         # объявляем очередь (если её нет)
  43.         self.pages_channel.queue_declare(queue='pages', durable=True)
  44.         # прилепляем к очереди callback
  45.         self.pages_channel.basic_consume(self.proc, queue='pages')
  46.  
  47.     def start(self):
  48.         """
  49.        Старт демона
  50.        """
  51.  
  52.         try:
  53.             self.pages_channel.start_consuming()
  54.         except Exception as error_msg:
  55.             print('start_consuming(): ', error_msg)
  56.             self.pages_channel.stop_consuming()
  57.  
  58.         self.connection.close()
  59.  
  60.     def proc(self, channel, method_frame, header_frame, message_json):
  61.         try:
  62.             page_dict = json.loads(str(message_json,'utf-8'))
  63.  
  64.             if 'source' not in page_dict:
  65.                 channel.basic_ack(delivery_tag=method_frame.delivery_tag)
  66.                 return False
  67.  
  68.             if page_dict['source'] == 'avito':
  69.                 result = self.proc_page(page_dict)
  70.         except Exception as ErrMsg:
  71.             print('proc(): ', ErrMsg)
  72.             channel.basic_ack(delivery_tag=method_frame.delivery_tag)
  73.             return False
  74.  
  75.         channel.basic_ack(delivery_tag=method_frame.delivery_tag)
  76.         return True
  77.  
  78.     def get_via_proxy(self, url, **kwargs):
  79.         """
  80.        Функция выполняет GET-запрос с использованием прокси из RabbitMQ
  81.        """
  82.  
  83.         # список User-Agent для запроса (выбираем рандомно)
  84.         user_agents = [
  85.             'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:53.0) Gecko/20100101 Firefox/53.0',
  86.             'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/59.0.3071.115 Safari/537.36',
  87.             'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.137 YaBrowser/17.4.1.758 Yowser/2.5 Safari/537.36',
  88.             'Mozilla/5.0 (Windows NT 10.0; WOW64; rv:45.0) Gecko/20100101 Firefox/45.0',
  89.             'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36'
  90.         ]
  91.  
  92.         if 'mobile' in kwargs and kwargs['mobile'] == True:
  93.             # переопределяем заголовки на мобильные если запрос идет на мобильную страницу
  94.             user_agents = [
  95.                 'Mozilla/5.0 (Linux; Android 6.0.1; SM-G920V Build/MMB29K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.98 Mobile Safari/537.36',
  96.                 'Mozilla/5.0 (Windows Phone 10.0; Android 4.2.1; Microsoft; Lumia 950) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2486.0 Mobile Safari/537.36 Edge/13.10586',
  97.                 'Mozilla/5.0 (Linux; Android 5.1.1; SM-G928X Build/LMY47X) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/47.0.2526.83 Mobile Safari/537.36',
  98.                 'Mozilla/5.0 (Linux; Android 6.0.1; Nexus 6P Build/MMB29P) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/47.0.2526.83 Mobile Safari/537.36',
  99.                 'Mozilla/5.0 (Linux; Android 6.0.1; E6653 Build/32.2.A.0.253) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.98 Mobile Safari/537.36',
  100.                 'Mozilla/5.0 (Linux; Android 6.0; HTC One M9 Build/MRA58K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.98 Mobile Safari/537.36'
  101.             ]
  102.  
  103.         # заголовки для запроса
  104.         request_headers = {
  105.             "accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8",
  106.             "accept-language": "ru-RU,ru;q=0.8,en-US;q=0.6,en;q=0.4",
  107.             "cache-control": "no-cache",
  108.             "upgrade-insecure-requests": "1"
  109.         }
  110.  
  111.         # выбираем рандомный User-Agent
  112.         request_headers['user-agent'] = random.choice(user_agents)
  113.  
  114.         # получаем свободный прокси из RabbitMQ через наше местное соединение
  115.         proxy = Proxy.get_proxy(self.connection)
  116.  
  117.         # формируем прокси для запроса
  118.         proxy_string = 'http://%s:%s@%s:%s' % ( proxy['login'], proxy['password'], proxy['ip'], str(proxy['port']) )
  119.         proxies = { 'https': proxy_string }
  120.  
  121.         response = None
  122.  
  123.         # выполняем запрос через прокси с левыми заголовками
  124.         try:
  125.             response = requests.get(url, proxies=proxies, headers=request_headers, timeout=15.0)
  126.         except Exception:
  127.             pass
  128.  
  129.         # освобождаем поюзанный прокси
  130.         Proxy.exempt_proxy(proxy['ip'])
  131.  
  132.         return response
  133.  
  134.     def proc_page(self, page_dict):
  135.         # получаем список урлов со страницы
  136.         subject_urls = []
  137.         try:
  138.             url = page_dict['url']
  139.             category = Category.objects.get(id=page_dict['category_id'])
  140.             page_response = self.get_via_proxy(url)
  141.             page_html = page_response.text
  142.             page_soup = BeautifulSoup(page_html, 'lxml')
  143.  
  144.             subject_url_soups = page_soup.find('div', class_='catalog-list').find_all('div', class_='item_table')
  145.  
  146.             for subject_url_soup in subject_url_soups:
  147.                 try:
  148.                     subject_url = subject_url_soup.find('div', class_='description').find('h3').find('a').get('href')
  149.                 except Exception:
  150.                     continue
  151.                 subject_urls.append(subject_url)
  152.  
  153.             if len(subject_urls) == 0:
  154.                 raise Exception('subject_urls is empty!')
  155.  
  156.         except Exception as ErrMsg:
  157.             print('proc_page(): ', ErrMsg)
  158.             # если список урлов не получен, нечего обрабатывать. выходим отсюда
  159.             return False
  160.  
  161.         del page_response, page_html, page_soup, subject_url_soups
  162.  
  163.         for full_subject_url in subject_urls:
  164.  
  165.             subject_url = full_subject_url.split('?')[0]
  166.  
  167.             try:
  168.                 new_subject = AvitoSubject()
  169.                 new_subject.url = subject_url
  170.                 new_subject.category_id = str(category.id)
  171.  
  172.                 city = City.objects.get(avito_alias=page_dict['avito_city_alias'])
  173.  
  174.                 # определение в URL объявления запрошенного города
  175.                 # если URL объявления не содержит правильного avito_alias,
  176.                 # значит, нам пришла левота и мы её пропускаем
  177.                 city_alias = city.avito_alias
  178.  
  179.                 del city
  180.  
  181.                 if city_alias not in subject_url:
  182.                     continue
  183.  
  184.                 new_subject.log = [ time.strftime("%Y.%m.%d %H:%M:%S", time.localtime(time.time())) + ' URL добавлен' ]
  185.  
  186.                 new_subject.save()
  187.             except Exception as err:
  188.                 continue
  189.  
  190.             try:
  191.                 self.proc_subject(new_subject)
  192.             except Exception as ErrMsg:
  193.                 continue
  194.  
  195.             del subject_urls, new_subject
  196.  
  197.         return True
  198.  
  199.     def proc_subject(self, subject):
  200.         # получаем полный HTML
  201.         try:
  202.             full_url = 'https://www.avito.ru' + subject.url
  203.             full_response = self.get_via_proxy(full_url)
  204.             full_html = full_response.text
  205.             full_soup = BeautifulSoup(full_html, 'lxml')
  206.         except Exception as ErrMsg:
  207.             print('get full soup: ', ErrMsg)
  208.             return False
  209.  
  210.         del full_url, full_response, full_html
  211.  
  212.         # получаем мобильный HTML
  213.         try:
  214.             mobile_url = 'https://m.avito.ru' + subject.url
  215.             mobile_response = self.get_via_proxy(mobile_url, mobile=True)
  216.             mobile_html = mobile_response.text
  217.             mobile_soup = BeautifulSoup(mobile_html, 'lxml')
  218.         except Exception as ErrMsg:
  219.             print('get mobile soup: ', ErrMsg)
  220.             return False
  221.  
  222.         del mobile_url, mobile_response, mobile_html
  223.  
  224.         # получаем телефон
  225.         try:
  226.             tel_tag = mobile_soup.find('a', {'data-marker':'item-contact-bar/call'}).get('href').strip()
  227.             phone = tel_tag.split(':')[1]
  228.             subject.phone = ''.join([i if i.isdigit() else '' for i in phone])
  229.             subject.save()
  230.         except Exception as ErrMsg:
  231.             print('get phone: ', ErrMsg)
  232.             return False
  233.  
  234.         del tel_tag, phone
  235.  
  236.         # получаем имя автора
  237.         try:
  238.             subject.author_name = mobile_soup.find('span', {'data-marker':'seller-info/name'}).text.strip()
  239.             subject.save()
  240.         except Exception:
  241.             print('get author name: ', ErrMsg)
  242.  
  243.         # получаем заголовок
  244.         try:
  245.             subject.title = mobile_soup.find('h1', {'data-marker':'item-description/title'}).find('span').text.strip()
  246.             subject.save()
  247.         except Exception:
  248.             print('get title: ', ErrMsg)
  249.  
  250.         # получаем описание
  251.         try:
  252.             subject.description = mobile_soup.find('meta', {'property':'og:description'}).get('content').strip()
  253.             subject.save()
  254.         except Exception as ErrMsg:
  255.             print('get description: ', ErrMsg)
  256.  
  257.         # получаем цену
  258.         try:
  259.             subject.price = mobile_soup.find('span', {'data-marker':'item-description/price'}).text.strip()
  260.             subject.save()
  261.         except Exception as ErrMsg:
  262.             print('get price: ', ErrMsg)
  263.  
  264.         # получаем фотки
  265.         try:
  266.             photos = []
  267.             scripts = mobile_soup.find('body').findAll('script')
  268.             for script in scripts:
  269.                 script_text = script.text
  270.  
  271.                 if 'window.__initialData__ =' not in script_text:
  272.                     continue
  273.  
  274.                 json_content = script_text[25:-6]
  275.                 json_object = json.loads(json_content)
  276.  
  277.                 item = json_object['item']
  278.  
  279.                 if 'currentItem' in item:
  280.                     current_item = item['currentItem']
  281.                 elif 'item' in item:
  282.                     current_item = item['item']
  283.                 else:
  284.                     raise Exception('Фотки не получены (блок currentItem отсутствует)')
  285.  
  286.                 images = current_item['images']
  287.  
  288.                 for image in images:
  289.                     if '640x480' in image:
  290.                         photos.append(image['640x480'])
  291.                         continue
  292.                     if '1280x960' in image:
  293.                         photos.append(image['1280x960'])
  294.                         continue
  295.  
  296.                 del images
  297.  
  298.             del scripts
  299.  
  300.             if len(photos) == 0:
  301.                 raise Exception('photos are empty!')
  302.  
  303.             subject.photos = photos
  304.             subject.save()
  305.         except Exception as ErrMsg:
  306.             print('get photos: ', ErrMsg)
  307.  
  308.         del photos
  309.  
  310.         # получаем поля
  311.         try:
  312.             param_fields = {}
  313.  
  314.             param_fields_soups = full_soup.find('ul', class_='item-params-list').find_all('li', class_='item-params-list-item')
  315.  
  316.             for param_fields_soup in param_fields_soups:
  317.                 param = param_fields_soup.text.strip().split(':')
  318.  
  319.                 param_fields[param[0]] = param[1]
  320.  
  321.             del param_fields_soup
  322.  
  323.             if len(param_fields) == 0:
  324.                 raise Exception('param_fields are empty!')
  325.  
  326.             subject.fields = param_fields
  327.             subject.save()
  328.         except Exception as ErrMsg:
  329.             print('get fields: ', ErrMsg)
  330.  
  331.         del param_fields, full_soup, mobile_soup
  332.  
  333.         return True
  334.  
  335. def start_new_thread():
  336.     ad = AvitoDaemon()
  337.     ad.start()
  338.  
  339. if __name__ == '__main__':
  340.     for i in range(0, 5):
  341.         threading.Thread(target=start_new_thread).start()
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement