Kihagyás

10. gyakorlat

A gyakorlat anyaga

Projekt struktúráltsága

Egy komolyabb projekt általában úgy van struktúrálva, hogy a projekt mappáján belül a projekt neve mappában a forráskód van, míg a tests mappában vannak a tesztjeink, valahogy így:

az-en-projektem/
├── README.md
├── setup.py
├── az-en-projektem/
│   ├── a_module.py
│   └── another_module.py
├── tests/
│   ├── test_a_module.py
│   └── test_another_module.py

A setup.py fájlról azonban még nem hallottunk korábban. Ez az a fájl, ami segítségével a modulunk telepíthető lesz, platformtól függetlenül. Felfogható úgy, hogy mint a make Python verziójaként. Akinek ismerős a make telepítés, az a Python telepítőjével is jóban lesz, elég hasonlóak.

Make telepítés

make && make install

Telepítés setup.py segítségével

python setup.py build && python setup.py install

Ám általában ez a telepítési mód kerülendő, a setup.py egy standardizált dolog, a mindenféle csomagtelepítők megértik. Leggyakoribb használata lehet a pip install ./. A telepítés után a kész modulunkat importálhatjuk egyszerűen bármely más projektbe is (az adott környezeten belül). Illetve, ha parancssori alkalmazásról van szó, akkor simán használhatjuk a modul nevét is, mint telepített alkalmazást.

A setup.py elkészítéséről és tartalmáról most nem fogunk mélyebben tanulni, akit érdekel, az az alábbi linkeken megteheti:

Amennyire nekünk szükségünk lesz, azt a PyCharm segítségével is legenerálhatjuk, a Tools > Create setup.py menüpont alatt.

Ezt követően érdemes a packages részt beállítani, ezt kézzel is megtehetjük, vagy használhatunk erre is egy automatizált megoldást.

from setuptools import setup, find_packages

setup(
    name='kpo',
    version='',
    packages=find_packages(exclude=("tests",)),
    url='',
    license='',
    author='',
    author_email='',
    description='',
    classifiers=[
        "Programming Language :: Python :: 3",
        "License :: OSI Approved :: MIT License",
        "Operating System :: OS Independent",
    ],
    python_requires=">=3.6",
)

Ezen kívül beállíthatunk különböző trove classifier-eket a projekthez, amik segítségével kategorizálhatjuk a csomagot, mire való, milyen operációs rendszeren fut, stb. Erre igaziból akkor lehet szükség, szeretnénk a kész csomagot a PyPI oldalon publikálni.

Elérhető classifierek: https://pypi.org/pypi?%3Aaction=list_classifiers

Bővebben a projektünk csomagolásáról: https://packaging.python.org/tutorials/packaging-projects

Tox

https://tox.readthedocs.io/en/latest/ Tesztautomatizálás céljából készült modul. Támogatja a különböző tesztkörnyezeteket, tesztfeladatokat.

[tox]
envlist = py38

[testenv]
deps =
    pytest
    mock
commands =
    pytest
[tox]
envlist = py38, cover, alma
skipsdist = true

[testenv]
passenv = PYTHONPATH
;whitelist_externals = find
install_command = python -m pip install {opts} {packages}
deps = -r{toxinidir}/requirements.txt
commands =
;    find . -type f -name "*.py[c|o]" -delete
    pytest

[testenv:cover]
deps = pytest-cov
       -r{toxinidir}/requirements.txt
whitelist_externals = coverage
skip_install = true
commands =
    pytest --cov
    coverage report
    coverage html
    coverage erase
[tox]
envlist = py38,cover,flake8,pylint,bandit,linters
skipsdist = true

[testenv]
passenv = PYTHONPATH
;whitelist_externals = find
install_command = python -m pip install {opts} {packages}
deps = -r{toxinidir}/requirements.txt
commands =
;    find . -type f -name "*.py[c|o]" -delete
    pytest

[testenv:cover]
deps =
    {[testenv]deps}
    pytest-cov
whitelist_externals = coverage
skip_install = true
commands =
    pytest --cov kpo/
    coverage report --fail-under=10
    coverage html
    coverage erase

[testenv:flake8]
deps =
    -r{toxinidir}/requirements.txt
    flake8
    flake8-bugbear
    flake8-docstrings>=1.3.1
    flake8-typing-imports>=1.1
    pep8-naming
commands =
    flake8 kpo/ tests/ setup.py

[testenv:bandit]
deps =
    {[testenv:flake8]deps}
    bandit
commands =
    bandit -r kpo/

[testenv:pylint]
skip_install = true
deps =
    pyflakes
    pylint!=2.5.0
commands =
    pylint --rcfile=tox.ini kpo/

[MESSAGES CONTROL]
; C0111 Missing docstring
; I0011: Locally disabling %s
; I0012: Locally enabling %s
disable=I0011,I0012,C0111,W0142,C

[flake8]
ignore = D100,D101,D102,D103,D104,D107,E501
max-complexity = 10
skip_install = true

[testenv:linters]
skip_install = true
deps =
    {[testenv:flake8]deps}
    {[testenv:pylint]deps}
    {[testenv:bandit]deps}
commands =
    {[testenv:flake8]commands}
    {[testenv:pylint]commands}
    {[testenv:bandit]commands}


;[testenv:doc8]
;skip_install = true
;deps =
;    sphinx
;    doc8
;commands =
;    doc8 docs/source/

;[testenv:docs]
;changedir = doc/
;deps =
;    -r {toxinidir}/doc/requirements.txt
;commands =
;    sphinx-build -W -b html -d _build/doctrees . _build/html

BeautifulSoup 4

Hivatalos dokumentáció: https://www.crummy.com/software/BeautifulSoup/bs4/doc/

import bs4
import requests

URL = r"https://www.tvmustra.hu/"


def main():
    resp = requests.get(URL)
    # ellenőrzések
    print(resp.text)
    # keresgélés?
    # regex?

    soup = bs4.BeautifulSoup(resp.text, "html.parser")
    print("text", soup.get_text())
    print("title", soup.find("title"))
    print("title", soup.title)  # Nem minden tag érhető el így!
    print("title text", soup.find("title").text)
    print("title str", soup.find("title").string)
    print("Összes kép az oldalon:", len(soup.find_all("img")))
    # Tag objektumok
    title = soup.find("title")
    print("tag neve", title.name)

    all_scripts = soup.find_all("script")
    elso = all_scripts[1]
    print(elso.name)
    print(elso['src'])

    print(soup.find_all("div", clazz='home_csatorna_musor_blokk'))
    print(soup.find_all("div", class_='home_csatorna_musor_blokk'))
    print(soup.find_all("div", attrs={'class': 'home_csatorna_musor_blokk'}))

    print("---")
    for event in soup.find_all("div", attrs={'class': 'home_csatorna_musor_blokk'}):
        if 'Hal a tortán' in event['title']:
            print(event)
            print("Keresett műsor megtalálva!")
            title_time = event['title'].replace('\n', ' ')
            print(f"-- {event['data-channel']} {title_time}")


if __name__ == '__main__':
    main()

Selenium

A Selenium alapvetően egy tesztelő keretrendszer, amit regressziós tesztekhez terveztek, és a segítségével különböző forgatókönyveket írunk le, amivel az amúgy kínkeserves kézi tesztelést úszhatjuk meg (amik alapvetően mindig emberi erőforrást igényelnének).

Nagyon sok mindenre jó, többek között:

  • Dinamikus oldalak betöltése (mivel gyakorlatilag egy böngészőn keresztül megy a dolog)
  • Adatok beírása formokba
  • Kattintás, drag and drop
  • Különböző várakozás

Tehát gyakorlatilag bármi dolgot le lehet vele programozni, amit kézzel csinálnánk. Azonban a Seleniumot data science szempontból fogjuk megnézni.

pip install selenium

Hivatalos dokumentáció: https://selenium-python.readthedocs.io/installation.html#introduction

Mivel a használatához szükséges egy speciális böngésző, ezért ezt is le kell töltenünk, és be kell állítanunk.

Gecko driver: https://github.com/mozilla/geckodriver/releases

A letöltött drivert ki kell csomagolni, és be kell tenni az útvonalát a PATH környezeti változóba.

Másik megoldás, hogy webdriver managereket használunk, ami roppant egyszerű és kényelmes megoldás. A használatához telepítünk kell a manager csomagot, a pip install webdriver-manager (a csomagról bővebben itt: https://pypi.org/project/webdriver-manager/)

from selenium import webdriver
from webdriver_manager.chrome import ChromeDriverManager
from webdriver_manager.firefox import GeckoDriverManager

driver = webdriver.Chrome(ChromeDriverManager().install())
driver.get("https://google.com")
driver.close()

driver = webdriver.Firefox(executable_path=GeckoDriverManager().install())
driver.get("https://google.com")
driver.close()
import bs4
from selenium import webdriver

URL = r"https://www.netpincer.hu/city/szeged"


def main():
    driver = webdriver.Firefox()
    driver.get(URL)
    html = driver.page_source
    soup = bs4.BeautifulSoup(html, "html.parser")
    all_restaurants = soup.find("section", attrs={'class': 'vendor-list-section'})
    for etterem in all_restaurants.find_all('li'):
        piros_sav = etterem.find('div', attrs={'class': 'tag-container'})
        if piros_sav:
            for kedvezmeny_jelzo in ["kedvezmény", "ingyen", "ajándék"]:
                if kedvezmeny_jelzo in piros_sav.text.lower():
                    print("Kedvezmény van!")
                    # CSS Selector
                    # Másolás a böngészőből
                    fig_caption = etterem.select('a:nth-child(1) > figure:nth-child(1) > figcaption:nth-child(2) > span:nth-child(1) > span:nth-child(1)')
                    # print(fig_caption)
                    print(f"-- {fig_caption[0].text} - {piros_sav.text}")


if __name__ == '__main__':
    main()
import bs4
import yaml
from selenium import webdriver
# from selenium.webdriver.common.action_chains import ActionChains
# from selenium.webdriver.support.ui import WebDriverWait
# from selenium.webdriver.support import expected_conditions as EC
# from selenium.webdriver.common.by import By

URL = "https://www.coosp.etr.u-szeged.hu/"

with open("credentials.yaml", encoding="utf8") as fp:
    conf = yaml.load(fp, Loader=yaml.FullLoader)


def main():
    felvett_targyak = dict()

    driver = webdriver.Firefox()
    driver.get(URL)
    username = driver.find_element_by_id("username")
    username.send_keys(conf['user']['neptun'])
    password = driver.find_element_by_id("password")
    password.send_keys(conf['user']['password'])
    search_button = driver.find_element_by_xpath("//input[@type='submit' and @value='Belépés']")
    search_button.click()
    #
    # slider = driver.find_element_by_css_selector("div.slider")
    # move = ActionChains(driver)
    # move.click_and_hold(slider).move_by_offset(200, 0).release().perform()
    #
    # WebDriverWait(driver, 5).until(
    #     EC.visibility_of_element_located((By.CSS_SELECTOR, "div.sliderOk"))
    # )
    # table = driver.find_element_by_css_selector("#resultingTable").get_attribute('outerHTML')
    # soup = bs4.BeautifulSoup(table, "html.parser")

    html = driver.page_source
    soup = bs4.BeautifulSoup(html, "html.parser")

    scenes = soup.find("div", attrs={'id': 'scenetreecontainer'})
    for scene in scenes.find_all('div', attrs={'class': 'scene'}):
        title = scene.find('span', attrs={'class': 'title'})
        # print(title)
        # print(title['title'])
        if title.text not in felvett_targyak:
            felvett_targyak[title.text] = 0
        felvett_targyak[title.text] += 1

    print("Felvett tárgyaim")
    for targy, felvetelek_szama in felvett_targyak.items():
        if felvetelek_szama > 1:
            print(f"-- {targy}, {felvetelek_szama} alkalommal.")


if __name__ == '__main__':
    main()

YAML

Sokszor előfordul, hogy valamilyen konfigurációt, beállítást, stb. külön fájlba szeretnénk szervezni. Ehhez láttunk már megoldást, például bármilyen fájlt tudunk írni, olvasni. Láttuk a json csomagot, az is teljesen jól használható ilyesmi célokhoz. Egy másik jó megoldás yaml fájlok használata pl. a PyYAML segítségével, amit most megnézünk minimálisan, de természetesen a json is maximálisan jó megoldás lehetne jelen esetben.

pip install pyyaml

Ezt követően olvashatunk, írhatunk yaml fájlokat dictionaryből.

import json
import yaml

my_config = {
    "username": "JohnJRambo",
    "password": "Rocky4Adrian",
    "weapons": ["M60", "Browning M2", "SVD Dragunov", "M72 LAW"],
    "personal_info": {
        "image": "/home/rambo/img/me_myself_i.jpg",
        "adrian": "https://www.youtube.com/watch?v=tad3NI68dKA"
    }
}

with open("rambo_data.yaml", mode="w", encoding="utf8") as fp:
    yaml.dump(my_config, fp, yaml.CDumper)

with open("demo_yaml.yml", encoding="utf8") as fp:
    data = yaml.load(fp, Loader=yaml.FullLoader)

print(json.dumps(data, indent=2))

Bővebb leírás: https://pyyaml.org/wiki/PyYAMLDocumentation

Feladatok

  1. Tetszőleges egyszerű oldal letöltése és információkinyerés bs4-gyel. Az adatokat valamilyen struktúrába tárold is le, dolgozd is fel (adatosztály, dict), végezz rajta adatgyűjtést!
  2. Tetszőleges dinamikus oldal letöltése és információkinyerés seleniummal. Az adatokat valamilyen struktúrába tárold is le, dolgozd is fel (adatosztály, dict), végezz rajta adatgyűjtést!

Utolsó frissítés: 2021-04-22 09:50:21