Патч для Авито Парсера

Патч для Авито Парсера


import threading

import time

from pathlib import Path


import flet as ft

from loguru import logger


from lang import *

from load_config import save_avito_config, load_avito_config

from parser_cls import AvitoParse

from tg_sender import SendAdToTg

from version import VERSION



def main(page: ft.Page):

  page.title = f'Parser Avito v {VERSION}'

  page.window.icon = str(Path(__file__).parent / "assets" / "logo.ico")

  page.theme_mode = ft.ThemeMode.DARK

  page.vertical_alignment = ft.MainAxisAlignment.CENTER

  page.window.width = 1000

  page.window.height = 980

  page.window.min_width = 650

  page.window.min_height = 500

  page.padding = 20

  is_run = False

  stop_event = threading.Event()


  def set_up():

    """Загружает настройки из config.toml и применяет к интерфейсу"""

    try:

      config = load_avito_config("config.toml")

    except Exception as err:

      logger.error(f"Ошибка при загрузке конфига: {err}")

      return


    url_input.value = "\n".join(config.urls or [])

    tg_chat_id.value = "\n".join(config.tg_chat_id or [])

    tg_token.value = config.tg_token or ""

    count_page.value = str(config.count)

    keys_word_white_list.value = "\n".join(config.keys_word_white_list or [])

    keys_word_black_list.value = "\n".join(config.keys_word_black_list or [])

    max_price.value = str(config.max_price)

    min_price.value = str(config.min_price)

    geo.value = config.geo or ""

    proxy.value = config.proxy_string or ""

    proxy_change_ip.value = config.proxy_change_url or ""

    pause_general.value = config.pause_general or 60

    pause_between_links.value = config.pause_between_links or 5

    max_age.value = config.max_age or 0

    seller_black_list.value = "\n".join(config.seller_black_list or [])

    ignore_ads_in_reserv.value = config.ignore_reserv or True

    ignore_promote_ads.value = config.ignore_promotion or False


    page.update()


  def to_int_safe(value, default=0):

    try:

      return int(value)

    except (ValueError, TypeError):

      return default


  def save_config():

    """Сохраняет настройки в TOML"""

    config = {"avito": {

      "tg_token": tg_token.value or "",

      "tg_chat_id": tg_chat_id.value.splitlines() if tg_chat_id.value else [],

      "urls": url_input.value.splitlines() if url_input.value else [],

      "count": to_int_safe(count_page.value, 1),

      "keys_word_white_list": keys_word_white_list.value.splitlines() if keys_word_white_list.value else [],

      "keys_word_black_list": keys_word_black_list.value.splitlines() if keys_word_black_list.value else [],

      "seller_black_list": seller_black_list.value.splitlines() if seller_black_list.value else [],

      "max_price": to_int_safe(max_price.value, 99999999),

      "min_price": to_int_safe(min_price.value, 0),

      "geo": geo.value or "",

      "proxy_string": proxy.value or "",

      "proxy_change_url": proxy_change_ip.value or "",

      "pause_general": to_int_safe(pause_general.value, 3),

      "pause_between_links": to_int_safe(pause_between_links.value, 1),

      "max_age": to_int_safe(max_age.value, 0),

      "max_count_of_retry": to_int_safe(max_count_of_retry.value, 5),

      "ignore_reserv": ignore_ads_in_reserv.value,

      "ignore_promotion": ignore_promote_ads.value

    }}


    save_avito_config(config)

    logger.debug("Настройки сохранены в config.toml")


  def close_dlg(e):

    dlg_modal_proxy.open = False

    page.update()


  def logger_console_init():

    logger.add(logger_console_widget, format="{time:HH:mm:ss} - {message}")


  def logger_console_widget(message):

    console_widget.value += message

    page.update()


  def telegram_log_test(e):

    """Тестирование отправки сообщения в telegram"""

    logger.info("Сейчас будет проверка данных telegram")

    token = tg_token.value

    chat_id = tg_chat_id.value

    if all([token, chat_id]):

      SendAdToTg(

        bot_token=token,

        chat_id=chat_id.split()

      ).send_to_tg()

      return

    logger.info("Должны быть заполнены поля ТОКЕН TELEGRAM и CHAT ID TELEGRAM")


  dlg_modal_proxy = ft.AlertDialog(

    modal=True,

    title=ft.Text("Подробнее насчёт прокси"),

    content=ft.Container(

      content=ft.Text(BUY_PROXY_LINK, size=20),

      width=600,

      height=400,

      padding=10

    ),

    actions=[

      ft.TextButton("Купить прокси",

             on_click=lambda e: page.launch_url(

               PROXY_LINK)),

      ft.TextButton("Отмена", on_click=close_dlg),


    ],

    actions_alignment=ft.MainAxisAlignment.END,

    on_dismiss=lambda e: print("Modal dialog dismissed!"),

  )


  def open_dlg_modal(e):

    page.overlay.append(dlg_modal_proxy)

    dlg_modal_proxy.open = True

    page.update()


  def start_parser(e):

    nonlocal is_run

    result = check_string()

    if not result:

      return

    logger.info("Старт")

    stop_event.clear()

    save_config()

    console_widget.height = 700

    input_fields.visible = False

    start_btn.visible = False

    stop_btn.visible = True

    is_run = True

    page.update()

    while is_run and not stop_event.is_set():

      run_process()

      if not is_run:

        return

      logger.info("Пауза между повторами")

      for _ in range(to_int_safe(pause_general.value, 300)):

        time.sleep(1)

        if not is_run:

          logger.info("Завершено")

          start_btn.text = "Старт"

          start_btn.disabled = False

          page.update()

          return


  def stop_parser(e):

    nonlocal is_run

    stop_event.set()

    logger.debug("Стоп")

    is_run = False

    console_widget.height = 100

    input_fields.visible = True

    stop_btn.visible = False

    start_btn.visible = True

    start_btn.text = "Останавливаюсь..."

    start_btn.disabled = True

    page.update()[/LEFT]


[LEFT]  def check_string():

    """Проверяет корректность введенных данных перед запуском."""

    # Можно добавить сюда другие проверки в будущем, если потребуется.

    return True


  def run_process():

    config = load_avito_config("config.toml")

    parser = AvitoParse(config, stop_event=stop_event)

    parsing_thread = threading.Thread(target=parser.parse)

    parsing_thread.start()

    parsing_thread.join()

    start_btn.disabled = False

    start_btn.text = "Старт"

    page.update()


  label_required = ft.Text("Обязательные параметры", size=20)

  url_input = ft.TextField(

    label="Вставьте начальную ссылку или ссылки. Используйте Enter между значениями",

    multiline=True,

    min_lines=3,

    max_lines=100,

    expand=True,

    tooltip=URL_INPUT_HELP,

    text_size=12,

    height=70,


  )

  min_price = ft.TextField(label="Минимальная цена", width=400, expand=True, text_size=12, height=40,

               tooltip=MIN_PRICE_HELP)

  max_price = ft.TextField(label="Максимальная цена", width=400, expand=True, text_size=12, height=40,

               tooltip=MAX_PRICE_HELP)

  label_not_required = ft.Text("Дополнительные параметры", height=30)

  keys_word_white_list = ft.TextField(

    label="Ключевые слова (через Enter)",

    multiline=True,

    min_lines=1,

    max_lines=50,

    width=400,

    expand=True,

    tooltip=KEYWORD_INPUT_HELP,

    text_size=12, height=60,

  )

  keys_word_black_list = ft.TextField(

    label="Черный список ключевых слов (через Enter)",

    multiline=True,

    min_lines=1,

    max_lines=50,

    width=400,

    expand=True,

    tooltip=KEYWORD_BLACK_INPUT_HELP,

    text_size=12, height=60,

  )

  count_page = ft.TextField(label="Количество страниц", width=400, expand=True, tooltip=COUNT_PAGE_HELP, text_size=12,

               height=40, )

  pause_general = ft.TextField(label="Пауза в секундах между повторами", width=400, expand=True, text_size=12,

                 height=40, tooltip=PAUSE_GENERAL_HELP)

  pause_between_links = ft.TextField(label="Пауза в секундах между каждой ссылкой", width=400, text_size=12,

                    height=40, expand=True, tooltip=PAUSE_BETWEEN_LINKS_HELP)


  max_age = ft.TextField(label="Макс. возраст объявления (в сек.)", width=400, text_size=12, height=40, expand=True,

              tooltip=MAX_AGE_HELP)

  max_count_of_retry = ft.TextField(label="Макс. кол-во повторов", width=400, text_size=12, height=40, expand=True,

                   tooltip=MAX_COUNT_OF_RETRY_HELP)

  tg_token = ft.TextField(label="Token telegram", width=400, text_size=12, height=40, expand=True,

              tooltip=TG_TOKEN_HELP)

  tg_chat_id = ft.TextField(label="Chat id telegram. Можно несколько", width=400,

               multiline=True, expand=True, text_size=12, height=40, tooltip=TG_CHAT_ID_HELP)

  btn_test_tg = ft.ElevatedButton(text="Проверить tg", disabled=False, on_click=telegram_log_test, expand=True,

                  tooltip=BTN_TEST_TG_HELP)


  proxy = ft.TextField(

    label="Прокси: protocol://user:pass@host:port",

    width=400,

    expand=True,

    tooltip="Поддерживаются http, https, socks4, socks5. Пример: http://user:pass@1.2.3.4:5678"

  )


  proxy_change_ip = ft.TextField(

    label="Ссылка для смены IP прокси (если поддерживается)",

    width=400,

    expand=True,

    tooltip=PROXY_CHANGE_IP_HELP

  )

  proxy_btn_help = ft.ElevatedButton(text="Подробнее про прокси", on_click=open_dlg_modal, expand=True,

                    tooltip=PROXY_BTN_HELP_HELP)

  geo = ft.TextField(label="Ограничение по городу", width=400, expand=True, text_size=12, height=40,

            tooltip=GEO_HELP)

  seller_black_list = ft.TextField(

    label="Черный список продавцов (через Enter)",

    multiline=True,

    min_lines=1,

    max_lines=100,

    expand=True,

    tooltip=BLACK_LIST_OF_SELLER_HELP,

    text_size=12,

    height=60,

  )

  start_btn = ft.FilledButton("Старт", width=800, on_click=start_parser, expand=True)

  stop_btn = ft.OutlinedButton("Стоп", width=980, on_click=stop_parser, visible=False,

                 style=ft.ButtonStyle(bgcolor=ft.colors.RED_400), expand=True)

  console_widget = ft.Text(width=800, height=80, color=ft.colors.GREEN, value="", selectable=True,

               expand=True) # , bgcolor=ft.colors.GREY_50)


  buy_me_coffe_btn = ft.TextButton("Продвинуть разработку",

                   on_click=lambda e: page.launch_url(DONAT_LINK),

                   style=ft.ButtonStyle(color=ft.colors.GREEN_300), expand=True,

                   tooltip=BUY_ME_COFFE_BTN_HELP)

  report_issue_btn = ft.TextButton("Сообщить о проблеме", on_click=lambda e: page.launch_url(

    "https://github.com/Duff89/parser_avito/issues"), style=ft.ButtonStyle(color=ft.colors.GREY), expand=True,

                   tooltip=REPORT_ISSUE_BTN_HELP)

  ignore_ads_in_reserv = ft.Checkbox(label="Игнорировать резервы", value=True, tooltip=IGNORE_RESERV_HELP)

  ignore_promote_ads = ft.Checkbox(label="Игнорировать продвинутые", value=False)


  input_fields = ft.Column(

    [

      label_required,

      url_input,

      ft.Row(

        [min_price, max_price],

        alignment=ft.MainAxisAlignment.CENTER,

        spacing=0

      ),

      # ft.Text(""),

      label_not_required,


      ft.Row(

        [keys_word_white_list, keys_word_black_list],

        alignment=ft.MainAxisAlignment.CENTER,

        spacing=0

      ),

      ft.Row(

        [count_page, pause_general],

        alignment=ft.MainAxisAlignment.CENTER,

        spacing=0

      ),

      ft.Row(

        [geo, pause_between_links],

        alignment=ft.MainAxisAlignment.CENTER,

        spacing=0

      ),

      ft.Row(

        [max_age, max_count_of_retry],

        alignment=ft.MainAxisAlignment.CENTER,

        spacing=0

      ),

      seller_black_list,

      ft.Row(

        [tg_token, tg_chat_id],

        alignment=ft.MainAxisAlignment.CENTER,

        spacing=0

      ),

      btn_test_tg,

      ft.Row(

        [proxy, proxy_change_ip],

        alignment=ft.MainAxisAlignment.CENTER,

        spacing=0

      ),

      proxy_btn_help,

      ft.Row(

        [ignore_ads_in_reserv, ignore_promote_ads],

        alignment=ft.MainAxisAlignment.CENTER,

        spacing=0

      ),


    ],

    expand=True,

    alignment=ft.MainAxisAlignment.CENTER,

    horizontal_alignment=ft.CrossAxisAlignment.CENTER,


  )


  controls = ft.Column(

    [console_widget,

     start_btn,

     stop_btn],

    expand=True,

    alignment=ft.MainAxisAlignment.CENTER,

    horizontal_alignment=ft.CrossAxisAlignment.CENTER

  )

  other_btn = ft.Row([buy_me_coffe_btn, report_issue_btn], expand=True, alignment=ft.MainAxisAlignment.CENTER)

  all_field = ft.Column([

    other_btn,

    input_fields,

    controls,

  ], alignment=ft.MainAxisAlignment.CENTER,

             horizontal_alignment=ft.CrossAxisAlignment.CENTER)


  def start_page():

    page.add(ft.Column(

      [all_field],

      expand=True,

      alignment=ft.MainAxisAlignment.CENTER,

      horizontal_alignment=ft.CrossAxisAlignment.CENTER,

      scroll=ft.ScrollMode.AUTO

    ))


  set_up()

  start_page()

  logger_console_init()



ft.app(

  target=main,

  assets_dir="assets",

)



Report Page