Полезное

CSS

Animatable CSS properties
Добавление шрифтов на сайт
Современный справочник по HTML и CSS

 Параметры requests.* методов

url: str | bytes - обязательный параметр, адрес куда стучимся

params: Any - get-параметры (?field1=value1&field2=value2) в виде словаря

data: Any | None - для передачи параметров POST, PUT

headers: Any | None - передача заголовков

cookies: Any | None - передача куки

files: Any | None - передача файла

auth: Any | None - кортеж для включения HTTP-аутентификации

timeout: Any | None - время ожидания отклика от сайта

allow_redirects: bool - разрешать редирект от этой страницы

proxies: Any | None - словарь протокола к URL-адресу прокси

hooks: Any | None - перехватчик событий

stream: Any | None - получить необработанный ответ сокета от сервера

verify: Any | None - логическое или строковое указание для проверки сертификата TLS сервера или нет

cert: Any | None - строка или кортеж, определяющий файл сертификата или ключ

json: Any | None - передача json


Остальные методы (по аналогии)

import requests

# GET

r = requests.get('url', params={"field1": "data1"})

# POST

r = requests.post('url', data={"field1": "data1"})

# UPDATE

r = requests.put('url', data={"field1": "data1"})

# PATCH аналогичен методу POST, но с двумя отличиями: он используется 

# для частичных изменений ресурса и его нельзя использовать в HTML-формах.

r = requests.patch('url', data={'field1': 'data1'})

# DELETE

r = requests.delete('url')

# Метод HEAD запрашивает ответ, идентичный запросу GET, но без тела ответа.

r = requests.head('url')


Метод select

import requests

from bs4 import BeautifulSoup as bs

r = requests.get('https://pythonworld.ru/samouchitel-python')

text = r.text

soup = bs(text, "html.parser")

# получить первый элемент, то можно получить так

print(soup.title.text)

# можно получить так

print(soup.select("head > title")[0].text)

# получить родителя

print(soup.title.parent.name)

# получить тег

print(soup.title.name)


print("---------------------------------------------------------")

# список мета-тегов

meta = soup.select("head > meta")

for m in meta:

    print(m)

    # attrs - выдает словарь всех аттрибутов тега: {"attr": "value", ...}

    for att, val in m.attrs.items():

        print("....", att, val)

    # можно удалить аттрибут

    # del m['id']


print("---------------------------------------------------------")

# поиск по классу

a1 = soup.select("p.strikeout.body")

Метод find

import requests
from bs4 import BeautifulSoup as bs

r = requests.get('https://pythonworld.ru/samouchitel-python')
text = r.text

soup = bs(text, "html.parser")

# вложенный поиск
print(soup.find("head").find("title"))

# поиск первого элемента
soup.find(id="link3")

Метод find_all

import requests
from bs4 import BeautifulSoup as bs

r = requests.get('https://pythonworld.ru/samouchitel-python')
text = r.text

soup = bs(text, "html.parser")

print("---------------------------------------------------------")
# все дети
for child in soup.head.children:
    print(child)
# все родители у первой ссылки
for parent in soup.find_all('a')[0].parents:
    print(parent.name)
# предыдущий тег на уровне
print(soup.find_all('meta')[0].previous_sibling)
# следующий тег на уровне
print(soup.find_all('meta')[0].next_sibling)

# сразу все по тегам в виде списка
print(soup.find_all(["a", "b"]))

# если нужны все элементы по атрибуту
news_list1 = soup.find_all('h2', id='post_title')
# если нужны все элементы по классу
news_list2 = soup.find_all('h2', class_='post__title')

# все по идентификатору
ids = soup.find_all(id="link2")

# все элементы с href через регулярку
import re
a1 = soup.find_all(href=re.compile("elsie"))

# все у кого есть атрибут id
a2 = soup.find_all(id=True)

# все ссылки
for link in soup.find_all('a'):
    print(link.get('href'))

# поиск по нескольким аттрибутам в виде словаря
a3 = soup.find_all(attrs={"data-foo": "value"})


# функция-фильтр поиска элементов
def has_six_characters(css_class):
    return css_class is not None and len(css_class) == 3


soup.find_all(class_=has_six_characters)

# установить ограничение на количество элементов
a4 = soup.find_all("a", limit=2)

Асинхронный парсер BS4 + aiohttp

# pip install aiohttp
# pip install fake_useragent

from bs4 import BeautifulSoup as BS
import aiohttp
import asyncio
from fake_useragent import UserAgent

url = "https://nsk.rbc.ru/"
h = {"User-Agent": UserAgent().random}


async def main():
    async with aiohttp.ClientSession() as session:
        async with session.get(url, headers=h) as response:
            content = await aiohttp.StreamReader.read(response.content)
            soup = BS(content, "html.parser")
            news = soup.find_all("span", {"class": "main__feed__title"})
            d = []
            for n in news:
                # получаем ссылку на новость
                link = n.parent.parent['href']
                # получаем заголовок новости
                header = n.text
                d.append((header, link))

            # делаем текст
            t = ''
            for i in d:
                t += i[0] + ';' + i[1] + '\n'
            # заносим текст в файл
            with open("data2.csv", "w", encoding="utf-8") as f:
                f.write(t)


if __name__ == "__main__":
    loop = asyncio.get_event_loop()
    loop.run_until_complete(main())

Динамический BS4 + selenium

Сайт, который был выбран для парсинга, случайный. У него есть штука, при загрузке появляется спинер (ожидание данных с сервера), потом только выходят данные. Для этого использовал задержку nime.sleep.

from bs4 import BeautifulSoup as BS
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.chrome.service import Service
from webdriver_manager.chrome import ChromeDriverManager
import time

URL = "https://www.onlinetrade.ru/catalogue/noutbuki-c9/"

o = Options()
o.add_experimental_option("detach", True)

driver = webdriver.Chrome(service=Service(ChromeDriverManager().install()), options=o)
# driver.maximize_window()
driver.get(URL)
# ставим задержку, чтобы прогрузилась страница
time.sleep(5)
text = driver.page_source

# создаем парсер
soup = BS(text, "html.parser")
# ищем все элементы по исследованному тегу и классу
items = soup.find_all("div", class_="indexGoods__item")
d = []
for n in items:
    # получаем название товара
    name = n.find("a", class_="indexGoods__item__name")
    # получаем цену в виде 45 006 Р
    price = n.find("span", itemprop="price")
    # обрабатываем цену
    price_edit = price.text.replace(" ", "")
    d.append((name.text, int(price_edit[:-1])))

# вывод всех данных
for i in d:
    print(i)

Парсер с обработкой ошибок от PythonToday 

import requests
from bs4 import BeautifulSoup
import time


def test_request(url, retry=5):
    headers = {
        "accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9",
        "user-agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.106 Safari/537.36"
    }

    try:
        response = requests.get(url=url, headers=headers)
        print(f"[+] {url} {response.status_code}")
    except Exception as ex:
        time.sleep(3)
        if retry:
            print(f"[INFO] retry={retry} => {url}")
            return test_request(url, retry=(retry - 1))
        else:
            raise
    else:
        return response


def main():
    with open("lesson10/books_urls.txt") as file:
        books_urls = file.read().splitlines()

    for book_url in books_urls:
        # test_request(url=book_url)

        try:
            r = test_request(url=book_url)
            soup = BeautifulSoup(r.text, "lxml")
            print(f"{soup.title.text}\n{'-' * 20}")
        except Exception as ex:
            continue


if __name__ == "__main__":
    main()

Асинхронный парсер от Denis

import asyncio
import aiohttp
import time


start_time = time.time()
all_data = []

async def get_page_data(session, category: str, page_id: int) -> str:
    if page_id:
        url = f'https://ozon.ru/brand/{category}/?page={page_id}'
    else:
        url = f'https://ozon.ru/brand/{category}/'
    async with session.get(url) as resp:
        assert resp.status == 200
        print(f'get url: {url}')
        resp_text = await resp.text()
        all_data.append(resp_text)
        return resp_text


async def load_site_data():
    categories_list = ['playstation-79966341', 'adidas-144082850', 'bosch-7577796', 'lego-19159896']
    async with aiohttp.ClientSession() as session:
        tasks = []
        for cat in categories_list:
            for page_id in range(100):
                # собираем все задачи в кучу
                task = asyncio.create_task(get_page_data(session, cat, page_id))
                tasks.append(task)
                # process text and do whatever we need...
        # Запускаем кучу задач
        await asyncio.gather(*tasks)


asyncio.run(load_site_data())

end_time = time.time() - start_time
print(all_data)
print(f"\nExecution time: {end_time} seconds")

Создание документа.

Создавать файл будем с помощью библиотеки fpdf. Установим библиотеку:

pip install fpdf
И напишем небольшой код:

import fpdf

# ориентация Portrait LandScape
# измерения mm pt cm in
# форматы А3 A4 A5 letter legal
pdf = fpdf.FPDF(orientation='P', unit='mm', format='A4')
# создаем страницу
pdf.add_page()

# устанавливаем шрифт
pdf.set_font("Arial", size=12)
# устанавливаем точку, рамку, выравнивание, текст, ссылку
pdf.cell(190, 1, border=True, fill=True, ln=1, align="C")
pdf.cell(10, 5, link='http://yandex.ru', txt="Welcome to Python!", ln=3, align="L")
pdf.cell(190, 1, border=True, fill=True, ln=1, align="C")

# добавляем свой шрифт
pdf.add_font('calibri', '', 'C:\\Windows\\Fonts\\calibri.ttf', uni=True)
pdf.set_font('calibri', size=18)
pdf.set_text_color(220, 50, 50)
pdf.cell(190, 15, txt="Welcome to Python!", ln=1, align="C")

# вставляем картинку
pdf.image("logo.png", x=10, y=80, w=100)

# рисуем
pdf.line(10, 10, 10, 100)
pdf.set_line_width(1)
pdf.set_draw_color(255, 128, 0)
pdf.line(20, 20, 100, 20)

pdf.ellipse(10, 40, 10, 100, 'F')
pdf.set_fill_color(230, 230, 0)
pdf.rect(30, 30, 100, 50)

pdf.output("simple_demo.pdf")

HTML в PDF.

from fpdf import FPDF, HTMLMixin

class MyFPDF(FPDF, HTMLMixin):
    pass

html = '''<h1 align="center">PyFPDF HTML Demo</h1>
    <p>This is regular text</p>
    <p>You can also <b>bold</b>, <i>italicize</i> or <u>underline</u>
    '''

pdf = MyFPDF()
pdf.add_page()
pdf.write_html(html)
pdf.output('html.pdf')

docx to pdf

import os
import comtypes.client

wdFormatPDF = 17

in_file = os.path.abspath("filename1.docx")
out_file = os.path.abspath("filename1.pdf")

word = comtypes.client.CreateObject('Word.Application')
doc = word.Documents.Open(in_file)
doc.SaveAs(out_file, FileFormat=wdFormatPDF)
doc.Close()
word.Quit()

xlsx to pdf

Вариант сохранения отдельного листа

import os
import comtypes.client

wdFormatPDF = 17

in_file = os.path.abspath("filename2.xlsx")
out_file = os.path.abspath("filename2.pdf")

excel = comtypes.client.CreateObject('Excel.Application')
excel.Visible = False
excel.DisplayAlerts = False
doc = excel.Workbooks.Open(in_file)
# обязательно указать нужный лист
doc.WorkSheets("New_sheet2").Select()
doc.ActiveSheet.ExportAsFixedFormat(0, out_file)
doc.Close()
excel.Quit()

Признаки "плохого кода"

Кратко рассмотрим 12 признаков, когда код можно улучшить: 1. Duplicated Code  — иногда повторяющийся код не всегда несет в себе пользу. Выде...