Парсим weblancer используя PROXY

    Цель работы


    1. Парсим сайт, используя прокси-сервера.
    2. Сохраняем данные в формате CSV.
    3. Пишем поисковик по найденным данным.
    4. Строим интерфейс.




    Использовать будем язык программирования Python. Сайт, с которого мы будем качать данные — www.weblancer.net (парсинг старой версии этого сайта был размещен здесь), в нем есть предложения работы по адресу www.weblancer.net/jobs. С него мы и будем получать данные — это название, цена, количество заявок, категория, краткое описание предлагаемой работы.

    Вход с использованием прокси означает — вход на сайт под ненастоящим адресом. Пригодится для парсинга сайта с защитой бана по IP адресу (то есть, если вы слишком часто, за короткий отрезок времени, входите на сайт).

    Импорт модулей



    Модули для непосредственно парсинга: requests и BeautifulSoup, их нам будет достаточно. Сохранять данные в формате csv нам поможет модуль с аналогичным названием — csv. В работе с интерфейсом нам поможет, до боли простой, модуль tkinter (кто желает получить более качественный интерфейс, советую воспользоваться модулем pyQt5). Работу по поиску и замене данных осуществит модуль re.

    import requests            #осуществляет работу с HTTP-запросами
    import urllib.request      #библиотека HTTP
    from lxml import html      #библиотека для обработки разметки xml и html, импортируем только для работы с html
    import re                  #осуществляет работу с регулярными выражениями
    from bs4 import BeautifulSoup    #осуществляет синтаксический разбор документов HTML
    import csv                 #осуществляет запись файла в формате CSV
    import tkinter             #создание интерфейса 
    from tkinter.filedialog import *     #диалоговые окна
    

    Переменные



    Создаем массив, где будем хранить уже использованные ранее прокси, и две текстовые переменные, к первой приравниваем адрес сайта, а вторую объявляем глобальной (есть варианты, когда использование глобальных переменных может негативно отразиться на работоспособности программы, подробнее о ее использовании — здесь), для получения ее данных в функциях.

    global proxy1     #объвляем глобальную переменную для запоминания прокси на следующий проход цикла
    proxy1 = ''       #и приравниваем к пустому тексту
    BASE_URL = 'https://www.weblancer.net/jobs/'     #адрес сайта для парсинга
    massiv = []       #массив для хранения прокси
    


    Переменные для tkinter:

    root = Tk()                                    #главное окно
    root.geometry('850x500')                       #ширина и высота главного окна в пикселях
    txt1 = Text(root, width = 18, heigh = 2)       #текстовое поле для ввода поисковых слов
    txt2 = Text(root, width = 60, heigh = 22)      #текстовое поле для вывода данных
    lbl4 = Label(root, text = '')                  #надпись для вывода прокси
    btn1 = Button(root, text = 'Отпарсить сайт')   #кнопка для парсинга
    btn2 = Button(root, text = 'Найти по слову')    #кнопка для поиска
    btn3 = Button(root, text = 'Очистить поля')     #кнопка для очистки полей
    lbl1 = Label(root, text = 'Впишите ключевые слова для поиска')      #надпись для поиска
    lbl2 = Label(root, text = '')                   #надпись для вывода процента парсинга
    lbl3 = Label(root, text = '')                   #надпись для вывода количества страниц
    


    Переменная.grid(строка, колонка) — определяем местоположение элемента в окне отображения. Bind — нажатие клавиши. Следующий код помещаем в самый конец программы:

    btn1.bind('<Button-1>', main)      #при нажатии клавиши вызывает основную функцию
    btn2.bind('<Button-1>', poisk)     #вызывает функцию поиска нужных заказов
    btn3.bind('<Button-1>', delete)    #вызывает функцию очистки полей
    
    lbl2.grid(row = 4, column = 1)
    lbl4.grid(row = 5, column = 1)
    lbl3.grid(row = 3, column = 1)
    btn1.grid(row = 1, column = 1)
    btn3.grid(row = 2, column = 1)
    btn2.grid(row = 1, column = 2)
    lbl1.grid(row = 2, column = 2)
    txt1.grid(row = 3, column = 2)
    txt2.grid(row = 6, column = 3)
    root.mainloop()                     #запуск приложения 
    


    Основная функция



    Первым делом, напишем главную функцию (почему функция, а не процедура? В будущем нам будет необходимо запускать ее с помощью bind (нажатие клавиши), это легче сделать именно с функцией), а позже будем добавлять прочие функции. Процедуры, которые нам пригодятся:

    • config — вносит изменения в элементы виджетов. К примеру, мы будем заменять текст в виджетах Label.
    • update — используется для обновления виджета. Столкнемся с проблемой — виджет будет изменен только после завершения цикла, update позволяет обновлять содержимое виджета каждый проход цикла.
    • re.sub(шаблон, изменяемая строка, строка) — находит шаблон в строке и заменяет его на указанную подстроку. Если шаблон не найден, строка остается неизменной.
    • get — осуществляет http-запрос, если он равен «200» — вход на сайт был удачен.
    • content — позволяет получить html-код.
    • L.extend(K) — расширяет список L, добавляя в конец все элементы списка K


    def main(event):     
    #запуск функции с передачей переменной event (для работы виджетов)
        page_count = get_page_count(get_html(BASE_URL))     
    #переменную присваиваем функции пересчета страниц, где сначала выполняется другая функция, получающая http-адрес от переменной BASE_URL
        lbl3.config(text='Всего найдено страниц: '+str(page_count))    
    #меняем текстовую часть переменной lbl3 на количество найденных страниц
        page = 1     
    #переменная для счетчика
        projects = []      
    #массив для хранения всей искомой информации
        while page_count != page:      
    #цикл выполняется, пока переменная page не равна количеству найденных страниц
            proxy = Proxy()      
    #присваиваем классу, где зададим нужные параметры
            proxy = proxy.get_proxy()      
    #получать proxy-адрес
            lbl4.update()     
    #обновляем виджет
            lbl4.config(text='Прокси: '+proxy)     
    #и приравниваем к полученному прокси
            global proxy1
    #глобальная переменная
            proxy1 = proxy       
    #приравниваем переменные для дальнейшей проверки их совпадения
            try:      #обработчик исключительных ситуаций
                for i in range(1,10):       
    #этот цикл будет прогонять полученный прокси определенное количество раз (range - определяет, сколько раз будем его использовать для входа на сайт). Можно и каждый раз брать новый прокси, но это существенно замедлит скорость работы программы
                    page += 1      
    #счетчик необходим для подсчета выполненной работы
                    lbl2.update()     
    #обновляем виджет
                    lbl2.config(text='Парсинг %d%%'%(page / page_count * 100))      
    #меняет процент сделанной работы от 100%
                    r = requests.get(BASE_URL + '?page=%d' % page, proxies={'https': proxy})
    #получаем данные со страницы сайта
                    parsing = BeautifulSoup(r.content, "lxml")     
    #получаем html-код по средству BeautifulSoup (чтобы позже использовать поисковые возможности этого модуля) для дальнейшей передачи переменной в функцию 
                    projects.extend(parse(BASE_URL + '?page=%d' % page, parsing))      
    #получаем данные из функции parse (передавая адрес страницы и html-код) и добавляем их в массив
                    save(projects, 'proj.csv')      
    #вызываем функцию сохранения данных в csv, передаем туда массив projects
            except requests.exceptions.ProxyError:       
    #неудача при подключеннии с прокси
                continue      #продолжаем цикл while
            except requests.exceptions.ConnectionError:     
    #не удалось сформировать запрос
                continue      #продолжаем цикл while
            except requests.exceptions.ChunkedEncodingError:      
    #сделана попытка доступа к сокету методом, запрещенным правами доступа
                continue      #продолжаем цикл while
    


    Подсчет страниц сайта



    Пишем функцию для получения url:

    def get_html(url):
    #объявление функции и передача в нее переменной url, которая является page_count[count]
        response = urllib.request.urlopen(url)
    #это надстройка над «низкоуровневой» библиотекой httplib, то есть, функция обрабатывает переменную для дальнейшего взаимодействия с самим железом
        return response.read()
    #возвращаем полученную переменную с заданным параметром read для корректного отображения
    


    Теперь с url ищем все страницы:

    def get_page_count(html):     
    #функция с переданной переменной html
       soup = BeautifulSoup(html, 'html.parser')     
    #получаем html-код от url сайта, который парсим
       paggination = soup('ul')[3:4]     
    #берем только данные, связанные с количеством страниц
       lis = [li for ul in paggination for li in ul.findAll('li')][-1]     
    #перебираем все страницы и заносим в массив lis, писать так циклы куда лучше для работоспособности программы
       for link in lis.find_all('a'):     
    #циклом ищем все данные связанные с порядковым номером страницы
           var1 = (link.get('href'))     
    #и присваиваем переменной
       var2 = var1[-3:]      
    #создаем срез, чтобы получить лишь число
       return int(var2)      
    #возвращаем переменную как числовой тип данных
    


    Получение прокси



    Код частично был взят у Игоря Данилова. Будем использовать __init__(self) — конструктор класса, где self — элемент, на место которого подставляется объект в момент его создания. Важно! __init__ по два подчеркивания с каждой стороны.

    class Proxy:      
    #создаем класс
        proxy_url = 'http://www.ip-adress.com/proxy_list/'     
    #переменной присваиваем ссылку сайта, выставляющего прокси-сервера
        proxy_list = []      
    #пустой массив для заполнения 
    
        def __init__(self):       
    #функция конструктора класса с передачей параметра self	
            r = requests.get(self.proxy_url)      
    #http-запрос методом get, запрос нужно осуществлять только с полным url
            str = html.fromstring(r.content)      
    #преобразование документа к типу lxml.html.HtmlElement
            result = str.xpath("//tr[@class='odd']/td[1]/text()")      
    #берем содержимое тега вместе с внутренними тегами для получение списка прокси
            for i in result:       
    #перебираем все найденные прокси
                if i in massiv:      
    #если есть совпадение с прокси уже использованными
                    yy = result.index(i)       
    #переменная равна индексу от совпавшего прокси в result
                    del result[yy]      
    #удаляем в result этот прокси
            self.list = result      
    #конструктору класса приравниваем прокси
            
        def get_proxy(self):
    #функция с передачей параметра self
            for proxy in self.list:
    #в цикле перебираем все найденные прокси
                if 'https://'+proxy == proxy1:
    #проверяем, совпдает ли до этого взятый прокси с новым, если да:
                        global massiv
    #massiv объявляем глобальным 
                        massiv = massiv + [proxy]
    #добавляем прокси к массиву
                url = 'https://'+proxy
    #прибавляем протокол к прокси
                return url
    #возвращаем данные
    


    Парсинг страниц



    Теперь находим на каждой странице сайта нужные нам данные. Новые процедуры:

    • find_all — в html-коде страницы ищет блоки и элементы, в нем находящиеся.
    • text — получение из html-кода только текст отображенный на сайте.
    • L.append(K) — добавляет элемент K в конец списка L.


    def parse(html,parsing):    
     #запуск функции с получением переменных html и parsing
       projects = []     
    #создаем пустой массив, где будем хранить все полученные данные
       table = parsing.find('div' , {'class' : 'container-fluid cols_table show_visited'})     
    #находим часть html-кода, хранящую название, категорию, цену, количество заявок, краткое описание
       for row in table.find_all('div' , {'class' : 'row'}):     
    #отбираем каждую запись
          cols = row.find_all('div')     
    #получаем название записи
          price = row.find_all('div' , {'class' : 'col-sm-1 amount title'})      
    #получаем цену записи
          cols1 = row.find_all('div' , {'class' : 'col-xs-12' , 'style' : 'margin-top: -10px; margin-bottom: -10px'})     
    #получаем краткое описание записи
          if cols1==[]:      
    #если массив остался пуст,
              application_text = ''      
    #то присваиваем пустую строку
          else:      #если не пуст
              application_text = cols1[0].text     
    #приравниваем к тексту из html-кода
          cols2 = [category.text for category in row.find_all('a' , {'class' : 'text-muted'})]      
    #с помощью цикла получаем категорию и заявку записи
          projects.append({'title': cols[0].a.text, 'category' : cols2[0], 'applications' : cols[2].text.strip(), 'price' : price[0].text.strip() , 'description' : application_text})      
    #в массив projects помещаем поочередно все найденные данные
       return projects     
    #возвращаем проект для сохранения
    


    Функция очистки



    Единственная, нам необходимая процедура delete — удаляет объект по указанному идентификатору или тегу.

    def delete(event):             #запуск функции
        txt1.delete(1.0, END)      #удаляет текст с вводимыми данными
        txt2.delete(1.0, END)      #удаляет текст с выведенными данными
    


    Поиск данных



    Функция будет осуществлять поиск предложений, в описании которых упоминаются необходимые нам слова. Запись в поле придется осуществлять с учетом знаний регулярных выражений (к примеру, python|Python, С\+\+).

    • csv.DictReader — конструктор возвращает объекты-итераторы для чтения
      данных из файла.
    • split — разбивает строку на части, используя разделитель, и возвращает эти части списком.
    • join — преобразовывает список в строку, рассматривая каждый элемент как строку.
    • insert — добавление элементы в список по индексу.


    def poisk(event):
    #запуск функции с передачей переменной event для работоспособности интерфейса
        file = open("proj.csv", "r")     
    #открытие файла, где мы сохранили все данные
        rdr = csv.DictReader(file, fieldnames = ['name', 'categori', 'zajavki', 'case', 'opisanie'])     
    #читаем данные из файла по столбцам
        poisk = txt1.get(1.0, END)     
    #получаем данные из поля для поиска соответствий
        poisk = poisk[0:len(r)-1]     
    #конкотенация необходима для отбрасывания последнего символа, который программа добавляет самостоятельно ('\n')
        for rec in rdr:      
    #запуск цикла, проход по каждой строке csv-файла
           data = rec['opisanie'].split(';')      
    #к переменной приравниваем данные по описанию задания
           data1 = rec['case'].split(';')       
    #к переменной приравниваем данные по цене задания
           data = ('').join(data)      
    #преобразовываем в строку
           data1 = ('').join(data1)      
    #преобразовываем в строку
           w = re.findall(poisk, data)      
    #ищем в описании совпадение с поисковыми словами
           if w != []:      
    #условие, если переменная w не равна пустому массиву, то продолжать
               if data1 == '':      
    #условие проверяющее, если цена не была получена, то продолжать
                   data1 = 'Договорная'       #заменяем пустое значение на текст
               txt2.insert(END, data+'--'+data1+'\n'+'---------------'+'\n')      
    #соединяем краткое описание заказа, его цену, переход на новую строку, символы, разделяющие заказы и снова переход на новую строку
    


    Сохранение данных



    Как уже говорил: данные будем сохранять в формате csv. При желании можно переписать функцию под любой другой формат.

    def save(projects, path):     
    #функция с переданной переменной и названием файла как переменная path
       with open(path, 'w') as csvfile:      
    #открываем файл как path и w (Открывает файл только для записи. Указатель стоит в начале файла. Создает файл с именем имя_файла, если такового не существует)
          writer = csv.writer(csvfile)      
    #writer - осуществляет запись файла, csv - определяет формат файла
          writer.writerow(('Проект', 'Категории', 'Заявки' , 'Цена' , 'Описание'))     
    #writerow - создает заглавия каждого заполняемого столбца
          for project in projects:      
    #перебираем элементы в массиве
              try:      
    #обработчик исключительных ситуаций
                  writer.writerow((project['title'], project['category'], project['applications'], project['price'], project['description']))     
    #каждому параметру присвоим данные
              except UnicodeEncodeError:      
    #в description иногда будут попадаться символы из других кодировок, придется брать как пустую строку
                  writer.writerow((project['title'], project['category'], project['applications'], project['price'], ''))     
    #каждому параметру присваиваем данные
    


    Надеюсь, данная информация будет полезна в вашей работе. Желаю удачи.

    AdBlock has stolen the banner, but banners are not teeth — they will be back

    More
    Ads

    Comments 7

      +7

      Жесть. Это просто какая-то мешанина из объяснений синтаксиса питона, отвратительного кода, избыточности комментариев и издевательств над языком. Не надо так писать, даже для себя, а уж тем более учить такому других.

        +3

        Очень хороший пример как не надо писать комментарии.

          +1
          Нормальные комментарии, малех избыточны, но лучше чем никаких, а для обучения -самое то. )))
            +1
            https://www.ozon.ru/context/detail/id/21916535/
            Строго рекомендую.
              0
              В заключении хорошо бы выдавать готовый скрипт, для тех, кто хотел бы опробовать результат.
                0
                Крайне рекомендую автору ознакомиться с PEP8.
                  0
                  Интересная постановка задачи, но код требует доработки в плане оптимизации.

                  Only users with full accounts can post comments. Log in, please.