diff --git a/genius.py b/genius.py new file mode 100644 index 0000000..a839d6a --- /dev/null +++ b/genius.py @@ -0,0 +1,56 @@ +import re + +from lxml import html, etree # type: ignore +import urllib3 + + +# Matches not only latin symbols, +# but also cyrillic alphabet, Chinese hyeroglyphics... +word_regex = re.compile(r'\w+') + +# Get all
s with lyrics +lyrics_xpath = etree.XPath('//div[@id="lyrics-root"][1]/div[@data-lyrics-container="true"]') + +# Get
s to add newlines after them for correct text parsing +br_xpath = etree.XPath('.//br') + + +def search(http: urllib3.PoolManager, title: str, artist: str) -> str: + '''Searches for Genius lyrics using SearXNG + Yahoo and returns the first URL. + Irrelevant texts should be picked manually''' + + resp = http.request( + 'GET', + 'https://searx.dc09.ru/search', + fields={ + 'q': artist + ' ' + title + ' site:genius.com', + 'engines': 'yahoo', + 'safesearch': '0', + 'format': 'json', + }, + ) + + result: dict[str, str] = resp.json()['results'][0] + del resp + + return result['url'] + + +def parse(http: urllib3.PoolManager, url: str) -> str: + '''Requests a lyrics page and parses with libxml2, + leaving only text and line breaks''' + + resp = http.request('GET', url) + tree = html.document_fromstring(resp.data) + divs = lyrics_xpath(tree) + del resp, tree + + return '\n'.join(_parse_text(div) for div in divs).strip() + + +def _parse_text(elem: html.HtmlElement) -> str: + + for br in br_xpath(elem): + br.tail = '\n' + (br.tail or '') + + return elem.text_content() diff --git a/test_genius.py b/test_genius.py new file mode 100644 index 0000000..34ee5d2 --- /dev/null +++ b/test_genius.py @@ -0,0 +1,42 @@ +from unittest import TestCase +import urllib3 + +import genius + + +TITLE = 'A Line In The Sand' +ARTIST = 'Linkin Park' +URL = 'https://genius.com/Linkin-park-a-line-in-the-sand-lyrics' + +LYR1 = '''[Intro: Mike Shinoda] +Today, we stood on the wall''' + +LYR2 = '''little did we know + +[Instrumental Break] + +[Verse 1:''' + +LYR3 = '''you are gonna get yours + +[Chorus: Chester Bennington] +Another day''' + + +class TestGenius(TestCase): + + def setUp(self) -> None: + self.http = urllib3.PoolManager() + + def test_search_success(self) -> None: + url = genius.search(self.http, TITLE, ARTIST) + self.assertEqual(url, URL) + + def test_lyrics_parsing(self) -> None: + lyrics = genius.parse(self.http, URL) + self.assertTrue(lyrics.startswith(LYR1)) + self.assertTrue(LYR2 in lyrics) + self.assertTrue(LYR3 in lyrics) + + def tearDown(self) -> None: + self.http.clear()