From 0c6155814e875238850ce013ba5d42c864bff443 Mon Sep 17 00:00:00 2001 From: sage Date: Wed, 13 Mar 2019 00:42:46 +0700 Subject: [PATCH 01/10] Pindahkan kbbi.py ke direktori kbbi --- kbbi/__init__.py | 3 +++ kbbi.py => kbbi/kbbi.py | 0 2 files changed, 3 insertions(+) create mode 100644 kbbi/__init__.py rename kbbi.py => kbbi/kbbi.py (100%) diff --git a/kbbi/__init__.py b/kbbi/__init__.py new file mode 100644 index 0000000..19f79c7 --- /dev/null +++ b/kbbi/__init__.py @@ -0,0 +1,3 @@ +from .kbbi import KBBI + +name = 'kbbi' diff --git a/kbbi.py b/kbbi/kbbi.py similarity index 100% rename from kbbi.py rename to kbbi/kbbi.py From 7e3a89775bbb5769228a880bbaa30e6b695b7047 Mon Sep 17 00:00:00 2001 From: sage Date: Wed, 13 Mar 2019 00:43:53 +0700 Subject: [PATCH 02/10] Rekonstruksi setup.py --- setup.py | 34 +++++++++++++++++++++++----------- 1 file changed, 23 insertions(+), 11 deletions(-) diff --git a/setup.py b/setup.py index d0349dc..b570b8b 100644 --- a/setup.py +++ b/setup.py @@ -2,22 +2,34 @@ File setup untuk modul KBBI. ''' -from setuptools import setup +from setuptools import find_packages, setup + +with open('README.md') as readme: + long_description = readme.read() + setup( name='kbbi', version='0.2.1', - py_modules=['kbbi'], + description=( + "A module that scraps a page in the online Indonesian dictionary (KBBI)" + ), + long_description=long_description, + long_description_content_type='text/markdown', + url='https://github.com/laymonage/kbbi-python', + author='sage', + author_email='laymonage@gmail.com', + classifiers=[ + "Programming Language :: Python :: 3", + "License :: OSI Approved :: MIT License", + "Operating System :: OS Independent", + "Natural Language :: Indonesian", + ], + keywords=( + 'kbbi kamus bahasa indonesia indonesian natural language scraper' + ), + packages=find_packages(), install_requires=[ 'requests', 'beautifulsoup4', ], - description=('A Python module that scraps an entry in KBBI' - '(https://kbbi.kemdikbud.go.id)'), - author='sage', - author_email='laymonage@gmail.com', - url='https://github.com/laymonage/kbbi-python', - download_url=('https://github.com/laymonage/kbbi-python/' - 'archive/0.2.1.tar.gz'), - keywords=['kbbi', 'kamus', 'indonesia'], - classifiers=[], ) From 377b861f6ab955e59473d0fadca179d4240e63b5 Mon Sep 17 00:00:00 2001 From: sage Date: Wed, 13 Mar 2019 00:44:27 +0700 Subject: [PATCH 03/10] Tambahkan Pipfile --- Pipfile | 14 +++++ Pipfile.lock | 175 +++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 189 insertions(+) create mode 100644 Pipfile create mode 100644 Pipfile.lock diff --git a/Pipfile b/Pipfile new file mode 100644 index 0000000..87b614d --- /dev/null +++ b/Pipfile @@ -0,0 +1,14 @@ +[[source]] +name = "pypi" +url = "https://pypi.org/simple" +verify_ssl = true + +[dev-packages] +pylint = "*" + +[packages] +requests = "*" +beautifulsoup4 = "*" + +[requires] +python_version = "3.7" diff --git a/Pipfile.lock b/Pipfile.lock new file mode 100644 index 0000000..75ba7ad --- /dev/null +++ b/Pipfile.lock @@ -0,0 +1,175 @@ +{ + "_meta": { + "hash": { + "sha256": "7647830aac752943892fbf0b2c0166fef76c6f5ec13c9a7c95756a0a6868a60d" + }, + "pipfile-spec": 6, + "requires": { + "python_version": "3.7" + }, + "sources": [ + { + "name": "pypi", + "url": "https://pypi.org/simple", + "verify_ssl": true + } + ] + }, + "default": { + "beautifulsoup4": { + "hashes": [ + "sha256:034740f6cb549b4e932ae1ab975581e6103ac8f942200a0e9759065984391858", + "sha256:945065979fb8529dd2f37dbb58f00b661bdbcbebf954f93b32fdf5263ef35348", + "sha256:ba6d5c59906a85ac23dadfe5c88deaf3e179ef565f4898671253e50a78680718" + ], + "index": "pypi", + "version": "==4.7.1" + }, + "certifi": { + "hashes": [ + "sha256:59b7658e26ca9c7339e00f8f4636cdfe59d34fa37b9b04f6f9e9926b3cece1a5", + "sha256:b26104d6835d1f5e49452a26eb2ff87fe7090b89dfcaee5ea2212697e1e1d7ae" + ], + "version": "==2019.3.9" + }, + "chardet": { + "hashes": [ + "sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae", + "sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691" + ], + "version": "==3.0.4" + }, + "idna": { + "hashes": [ + "sha256:c357b3f628cf53ae2c4c05627ecc484553142ca23264e593d327bcde5e9c3407", + "sha256:ea8b7f6188e6fa117537c3df7da9fc686d485087abf6ac197f9c46432f7e4a3c" + ], + "version": "==2.8" + }, + "requests": { + "hashes": [ + "sha256:502a824f31acdacb3a35b6690b5fbf0bc41d63a24a45c4004352b0242707598e", + "sha256:7bf2a778576d825600030a110f3c0e3e8edc51dfaafe1c146e39a2027784957b" + ], + "index": "pypi", + "version": "==2.21.0" + }, + "soupsieve": { + "hashes": [ + "sha256:afa56bf14907bb09403e5d15fbed6275caa4174d36b975226e3b67a3bb6e2c4b", + "sha256:eaed742b48b1f3e2d45ba6f79401b2ed5dc33b2123dfe216adb90d4bfa0ade26" + ], + "version": "==1.8" + }, + "urllib3": { + "hashes": [ + "sha256:61bf29cada3fc2fbefad4fdf059ea4bd1b4a86d2b6d15e1c7c0b582b9752fe39", + "sha256:de9529817c93f27c8ccbfead6985011db27bd0ddfcdb2d86f3f663385c6a9c22" + ], + "version": "==1.24.1" + } + }, + "develop": { + "astroid": { + "hashes": [ + "sha256:6560e1e1749f68c64a4b5dee4e091fce798d2f0d84ebe638cf0e0585a343acf4", + "sha256:b65db1bbaac9f9f4d190199bb8680af6f6f84fd3769a5ea883df8a91fe68b4c4" + ], + "version": "==2.2.5" + }, + "isort": { + "hashes": [ + "sha256:18c796c2cd35eb1a1d3f012a214a542790a1aed95e29768bdcb9f2197eccbd0b", + "sha256:96151fca2c6e736503981896495d344781b60d18bfda78dc11b290c6125ebdb6" + ], + "version": "==4.3.15" + }, + "lazy-object-proxy": { + "hashes": [ + "sha256:0ce34342b419bd8f018e6666bfef729aec3edf62345a53b537a4dcc115746a33", + "sha256:1b668120716eb7ee21d8a38815e5eb3bb8211117d9a90b0f8e21722c0758cc39", + "sha256:209615b0fe4624d79e50220ce3310ca1a9445fd8e6d3572a896e7f9146bbf019", + "sha256:27bf62cb2b1a2068d443ff7097ee33393f8483b570b475db8ebf7e1cba64f088", + "sha256:27ea6fd1c02dcc78172a82fc37fcc0992a94e4cecf53cb6d73f11749825bd98b", + "sha256:2c1b21b44ac9beb0fc848d3993924147ba45c4ebc24be19825e57aabbe74a99e", + "sha256:2df72ab12046a3496a92476020a1a0abf78b2a7db9ff4dc2036b8dd980203ae6", + "sha256:320ffd3de9699d3892048baee45ebfbbf9388a7d65d832d7e580243ade426d2b", + "sha256:50e3b9a464d5d08cc5227413db0d1c4707b6172e4d4d915c1c70e4de0bbff1f5", + "sha256:5276db7ff62bb7b52f77f1f51ed58850e315154249aceb42e7f4c611f0f847ff", + "sha256:61a6cf00dcb1a7f0c773ed4acc509cb636af2d6337a08f362413c76b2b47a8dd", + "sha256:6ae6c4cb59f199d8827c5a07546b2ab7e85d262acaccaacd49b62f53f7c456f7", + "sha256:7661d401d60d8bf15bb5da39e4dd72f5d764c5aff5a86ef52a042506e3e970ff", + "sha256:7bd527f36a605c914efca5d3d014170b2cb184723e423d26b1fb2fd9108e264d", + "sha256:7cb54db3535c8686ea12e9535eb087d32421184eacc6939ef15ef50f83a5e7e2", + "sha256:7f3a2d740291f7f2c111d86a1c4851b70fb000a6c8883a59660d95ad57b9df35", + "sha256:81304b7d8e9c824d058087dcb89144842c8e0dea6d281c031f59f0acf66963d4", + "sha256:933947e8b4fbe617a51528b09851685138b49d511af0b6c0da2539115d6d4514", + "sha256:94223d7f060301b3a8c09c9b3bc3294b56b2188e7d8179c762a1cda72c979252", + "sha256:ab3ca49afcb47058393b0122428358d2fbe0408cf99f1b58b295cfeb4ed39109", + "sha256:bd6292f565ca46dee4e737ebcc20742e3b5be2b01556dafe169f6c65d088875f", + "sha256:cb924aa3e4a3fb644d0c463cad5bc2572649a6a3f68a7f8e4fbe44aaa6d77e4c", + "sha256:d0fc7a286feac9077ec52a927fc9fe8fe2fabab95426722be4c953c9a8bede92", + "sha256:ddc34786490a6e4ec0a855d401034cbd1242ef186c20d79d2166d6a4bd449577", + "sha256:e34b155e36fa9da7e1b7c738ed7767fc9491a62ec6af70fe9da4a057759edc2d", + "sha256:e5b9e8f6bda48460b7b143c3821b21b452cb3a835e6bbd5dd33aa0c8d3f5137d", + "sha256:e81ebf6c5ee9684be8f2c87563880f93eedd56dd2b6146d8a725b50b7e5adb0f", + "sha256:eb91be369f945f10d3a49f5f9be8b3d0b93a4c2be8f8a5b83b0571b8123e0a7a", + "sha256:f460d1ceb0e4a5dcb2a652db0904224f367c9b3c1470d5a7683c0480e582468b" + ], + "version": "==1.3.1" + }, + "mccabe": { + "hashes": [ + "sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42", + "sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f" + ], + "version": "==0.6.1" + }, + "pylint": { + "hashes": [ + "sha256:5d77031694a5fb97ea95e828c8d10fc770a1df6eb3906067aaed42201a8a6a09", + "sha256:723e3db49555abaf9bf79dc474c6b9e2935ad82230b10c1138a71ea41ac0fff1" + ], + "index": "pypi", + "version": "==2.3.1" + }, + "six": { + "hashes": [ + "sha256:3350809f0555b11f552448330d0b52d5f24c91a322ea4a15ef22629740f3761c", + "sha256:d16a0141ec1a18405cd4ce8b4613101da75da0e9a7aec5bdd4fa804d0e0eba73" + ], + "version": "==1.12.0" + }, + "typed-ast": { + "hashes": [ + "sha256:035a54ede6ce1380599b2ce57844c6554666522e376bd111eb940fbc7c3dad23", + "sha256:037c35f2741ce3a9ac0d55abfcd119133cbd821fffa4461397718287092d9d15", + "sha256:049feae7e9f180b64efacbdc36b3af64a00393a47be22fa9cb6794e68d4e73d3", + "sha256:19228f7940beafc1ba21a6e8e070e0b0bfd1457902a3a81709762b8b9039b88d", + "sha256:2ea681e91e3550a30c2265d2916f40a5f5d89b59469a20f3bad7d07adee0f7a6", + "sha256:3a6b0a78af298d82323660df5497bcea0f0a4a25a0b003afd0ce5af049bd1f60", + "sha256:5385da8f3b801014504df0852bf83524599df890387a3c2b17b7caa3d78b1773", + "sha256:606d8afa07eef77280c2bf84335e24390055b478392e1975f96286d99d0cb424", + "sha256:69245b5b23bbf7fb242c9f8f08493e9ecd7711f063259aefffaeb90595d62287", + "sha256:6f6d839ab09830d59b7fa8fb6917023d8cb5498ee1f1dbd82d37db78eb76bc99", + "sha256:730888475f5ac0e37c1de4bd05eeb799fdb742697867f524dc8a4cd74bcecc23", + "sha256:9819b5162ffc121b9e334923c685b0d0826154e41dfe70b2ede2ce29034c71d8", + "sha256:9e60ef9426efab601dd9aa120e4ff560f4461cf8442e9c0a2b92548d52800699", + "sha256:af5fbdde0690c7da68e841d7fc2632345d570768ea7406a9434446d7b33b0ee1", + "sha256:b64efdbdf3bbb1377562c179f167f3bf301251411eb5ac77dec6b7d32bcda463", + "sha256:bac5f444c118aeb456fac1b0b5d14c6a71ea2a42069b09c176f75e9bd4c186f6", + "sha256:bda9068aafb73859491e13b99b682bd299c1b5fd50644d697533775828a28ee0", + "sha256:d659517ca116e6750101a1326107d3479028c5191f0ecee3c7203c50f5b915b0", + "sha256:eddd3fb1f3e0f82e5915a899285a39ee34ce18fd25d89582bc89fc9fb16cd2c6" + ], + "markers": "implementation_name == 'cpython'", + "version": "==1.3.1" + }, + "wrapt": { + "hashes": [ + "sha256:4aea003270831cceb8a90ff27c4031da6ead7ec1886023b80ce0dfe0adf61533" + ], + "version": "==1.11.1" + } + } +} From 52a3c46fa7c92fedd3d2dfec860c84fd9234f14c Mon Sep 17 00:00:00 2001 From: sage Date: Thu, 14 Mar 2019 20:48:41 +0700 Subject: [PATCH 04/10] Tulis ulang sepenuhnya --- kbbi/kbbi.py | 195 +++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 159 insertions(+), 36 deletions(-) diff --git a/kbbi/kbbi.py b/kbbi/kbbi.py index e2d04c2..4925ca9 100644 --- a/kbbi/kbbi.py +++ b/kbbi/kbbi.py @@ -2,60 +2,183 @@ kbbi-python https://kbbi.kemdikbud.go.id -Mengambil entri sebuah kata/frase dalam KBBI Daring. +Mengambil laman sebuah kata/frasa dalam KBBI Daring. Penggunaan: kata = KBBI('kata') ''' from urllib.parse import quote + import requests from bs4 import BeautifulSoup class KBBI: ''' - Sebuah entri di KBBI. + Sebuah laman di KBBI. ''' class TidakDitemukan(Exception): ''' - Galat yang menunjukkan bahwa entri tidak ditemukan dalam KBBI. + Galat yang menunjukkan bahwa laman tidak ditemukan dalam KBBI. ''' - def __init__(self, keyword): - Exception.__init__(self, keyword + ' tidak ditemukan dalam KBBI!') - - def __init__(self, keyword): - url = 'https://kbbi.kemdikbud.go.id/entri/' + quote(keyword) - raw = requests.get(url).text - if "Entri tidak ditemukan." in raw: - raise self.TidakDitemukan(keyword) - self.arti = [] - self.arti_contoh = [] - isolasi = raw[raw.find('

'):raw.find('

')] - soup = BeautifulSoup(isolasi, 'html.parser') - entri = soup.find_all('ol') + soup.find_all('ul') - - for tiap_entri in entri: - for tiap_arti in tiap_entri.find_all('li'): - kelas = tiap_arti.find(color="red").get_text().strip() - arti_lengkap = tiap_arti.get_text().strip()[len(kelas):] - - if ':' in arti_lengkap: - arti_saja = arti_lengkap[:arti_lengkap.find(':')] - else: - arti_saja = arti_lengkap - - if kelas: - hasil = '({0}) {1}' - else: - hasil = '{1}' - - self.arti_contoh.append(hasil.format(kelas, arti_lengkap)) - self.arti.append(hasil.format(kelas, arti_saja)) + def __init__(self, kata_kunci): + super().__init__(kata_kunci + ' tidak ditemukan dalam KBBI!') + + def __init__(self, kata_kunci): + url = 'https://kbbi.kemdikbud.go.id/entri/' + quote(kata_kunci) + laman = requests.get(url) + + if "Entri tidak ditemukan." in laman.text: + raise self.TidakDitemukan(kata_kunci) + + self.nama = kata_kunci.lower() + self.entri = [] + self._init_entri(laman) + + def _init_entri(self, laman): + sup = BeautifulSoup(laman.text, 'html.parser') + estr = '' + for label in sup.find('hr').next_siblings: + if label.name == 'hr': + self.entri.append(Entri(estr)) + break + if label.name == 'h2': + if estr: + self.entri.append(Entri(estr)) + estr = '' + estr += str(label).strip() + + def serialisasi(self): + return { + self.nama: [entri.serialisasi() for entri in self.entri] + } def __str__(self): - return '\n'.join(self.arti) + return '\n\n'.join(str(entri) for entri in self.entri) def __repr__(self): - return str(self) + return "".format(self.nama) + + +class Entri: + def __init__(self, entri_html): + entri = BeautifulSoup(entri_html, 'html.parser') + judul = entri.find('h2') + dasar = judul.find(class_='rootword') + nomor = judul.find('sup', recursive=False) + lafal = judul.find(class_='syllable') + bentuk_tidak_baku = judul.find('small') + makna = entri.find_all('li') + + self.nama = ambil_teks_dalam_label(judul) + self.nomor = nomor.text.strip() if nomor else '' + self.kata_dasar = '' + self._init_kata_dasar(dasar) + self.pelafalan = lafal.text.strip() if lafal else '' + + self.bentuk_tidak_baku = [] + if bentuk_tidak_baku: + bentuk_tidak_baku = bentuk_tidak_baku.find_all('b') + self.bentuk_tidak_baku = ''.join( + e.text.strip() for e in bentuk_tidak_baku + ).split(', ') + + self.makna = [Makna(m) for m in makna] + + def _init_kata_dasar(self, dasar): + if dasar: + dasar = dasar.find('a') + dasar_no = dasar.find('sup') + self.kata_dasar = ambil_teks_dalam_label(dasar) + if dasar_no: self.kata_dasar += ' [{}]'.format(dasar_no.text.strip()) + + def serialisasi(self): + return { + "nama": self.nama, + "nomor": self.nomor, + "kata_dasar": self.kata_dasar, + "pelafalan": self.pelafalan, + "bentuk_tidak_baku": self.bentuk_tidak_baku, + "makna": [makna.serialisasi() for makna in self.makna] + } + + def _makna(self): + if (len(self.makna) > 1): + return '\n'.join( + str(i) + ". " + str(makna) + for i, makna in enumerate(self.makna, 1) + ) + return str(self.makna[0]) + + def _nama(self): + hasil = self.nama + hasil += " [{}]".format(self.nomor) if self.nomor else '' + hasil = self.kata_dasar + " » " + hasil if self.kata_dasar else hasil + return hasil + + def _bentuk_tidak_baku(self): + return ( + 'Bentuk tidak baku: ' + ', '.join(self.bentuk_tidak_baku) + if self.bentuk_tidak_baku else '' + ) + + def __str__(self): + hasil = self._nama() + if self.pelafalan: hasil += ' ' + self.pelafalan + if self.bentuk_tidak_baku: hasil += '\n' + self._bentuk_tidak_baku() + return hasil + '\n' + self._makna() + + def __repr__(self): + return "".format(self._nama()) + + +class Makna: + def __init__(self, makna_label): + baku = makna_label.find('a') + kelas = makna_label.find(color='red').find_all('span') + submakna = ambil_teks_dalam_label(makna_label).rstrip(':') + + if baku: submakna += ' ' + baku.text.strip() + + self.kelas = {k.text.strip(): k['title'] for k in kelas} + self.submakna = submakna.split('; ') + self._init_contoh(makna_label) + + def _init_contoh(self, makna_label): + indeks = makna_label.text.find(': ') + if indeks != -1: + contoh = makna_label.text[indeks + 2:].strip() + self.contoh = contoh.split('; ') + else: + self.contoh = '' + + def serialisasi(self): + return { + "kelas": self.kelas, + "submakna": self.submakna, + "contoh": self.contoh + } + + def _kelas(self): + return ' '.join("({})".format(k) for k in self.kelas) + + def _submakna(self): + return '; '.join(self.submakna) + + def _contoh(self): + return '; '.join(self.contoh) + + def __str__(self): + hasil = self._kelas() + ' ' if self.kelas else '' + hasil += self._submakna() + hasil += ': ' + self._contoh() if self.contoh else '' + return hasil + + def __repr__(self): + return "".format('; '.join(self.submakna)) + + +def ambil_teks_dalam_label(sup): + return ''.join(i.strip() for i in sup.find_all(text=True, recursive=False)) From 191d01e7ec53038c5bc4912453a59d0b423ef2bf Mon Sep 17 00:00:00 2001 From: sage Date: Sat, 16 Mar 2019 09:46:31 +0700 Subject: [PATCH 05/10] Atasi kemungkinan Varian dan kelas prakategorial --- kbbi/kbbi.py | 91 ++++++++++++++++++++++++++++++++++------------------ 1 file changed, 60 insertions(+), 31 deletions(-) diff --git a/kbbi/kbbi.py b/kbbi/kbbi.py index 4925ca9..83a4792 100644 --- a/kbbi/kbbi.py +++ b/kbbi/kbbi.py @@ -2,7 +2,7 @@ kbbi-python https://kbbi.kemdikbud.go.id -Mengambil laman sebuah kata/frasa dalam KBBI Daring. +Mengambil sebuah laman untuk kata/frasa dalam KBBI Daring. Penggunaan: kata = KBBI('kata') @@ -66,33 +66,42 @@ class Entri: def __init__(self, entri_html): entri = BeautifulSoup(entri_html, 'html.parser') judul = entri.find('h2') - dasar = judul.find(class_='rootword') + dasar = judul.find_all(class_='rootword') nomor = judul.find('sup', recursive=False) lafal = judul.find(class_='syllable') - bentuk_tidak_baku = judul.find('small') - makna = entri.find_all('li') + varian = judul.find('small') + if entri.find(color='darkgreen'): + makna = [entri] + else: + makna = entri.find_all('li') self.nama = ambil_teks_dalam_label(judul) self.nomor = nomor.text.strip() if nomor else '' - self.kata_dasar = '' + self.kata_dasar = [] self._init_kata_dasar(dasar) self.pelafalan = lafal.text.strip() if lafal else '' self.bentuk_tidak_baku = [] - if bentuk_tidak_baku: - bentuk_tidak_baku = bentuk_tidak_baku.find_all('b') - self.bentuk_tidak_baku = ''.join( - e.text.strip() for e in bentuk_tidak_baku - ).split(', ') + self.varian = [] + if varian: + bentuk_tidak_baku = varian.find_all('b') + if bentuk_tidak_baku: + self.bentuk_tidak_baku = ''.join( + e.text.strip() for e in bentuk_tidak_baku + ).split(', ') + else: + self.varian = varian.text[len('varian: '):].strip().split(', ') self.makna = [Makna(m) for m in makna] def _init_kata_dasar(self, dasar): - if dasar: - dasar = dasar.find('a') - dasar_no = dasar.find('sup') - self.kata_dasar = ambil_teks_dalam_label(dasar) - if dasar_no: self.kata_dasar += ' [{}]'.format(dasar_no.text.strip()) + for d in dasar: + kata = d.find('a') + dasar_no = kata.find('sup') + kata = ambil_teks_dalam_label(kata) + self.kata_dasar.append( + kata + ' [{}]'.format(dasar_no.text.strip()) if dasar_no else kata + ) def serialisasi(self): return { @@ -101,6 +110,7 @@ def serialisasi(self): "kata_dasar": self.kata_dasar, "pelafalan": self.pelafalan, "bentuk_tidak_baku": self.bentuk_tidak_baku, + "varian": self.varian, "makna": [makna.serialisasi() for makna in self.makna] } @@ -114,20 +124,24 @@ def _makna(self): def _nama(self): hasil = self.nama - hasil += " [{}]".format(self.nomor) if self.nomor else '' - hasil = self.kata_dasar + " » " + hasil if self.kata_dasar else hasil + if self.nomor: hasil += " [{}]".format(self.nomor) + if self.kata_dasar: hasil = " » ".join(self.kata_dasar) + " » " + hasil return hasil - def _bentuk_tidak_baku(self): - return ( - 'Bentuk tidak baku: ' + ', '.join(self.bentuk_tidak_baku) - if self.bentuk_tidak_baku else '' - ) + def _varian(self, varian): + if varian == self.bentuk_tidak_baku: + nama = "Bentuk tidak baku" + elif varian == self.varian: + nama = "Varian" + else: + return '' + return nama + ': ' + ', '.join(varian) def __str__(self): hasil = self._nama() if self.pelafalan: hasil += ' ' + self.pelafalan - if self.bentuk_tidak_baku: hasil += '\n' + self._bentuk_tidak_baku() + for var in (self.bentuk_tidak_baku, self.varian): + if var: hasil += '\n' + self._varian(var) return hasil + '\n' + self._makna() def __repr__(self): @@ -136,23 +150,38 @@ def __repr__(self): class Makna: def __init__(self, makna_label): + self.submakna = ambil_teks_dalam_label(makna_label).rstrip(':') baku = makna_label.find('a') - kelas = makna_label.find(color='red').find_all('span') - submakna = ambil_teks_dalam_label(makna_label).rstrip(':') - - if baku: submakna += ' ' + baku.text.strip() - - self.kelas = {k.text.strip(): k['title'] for k in kelas} - self.submakna = submakna.split('; ') + if baku: + self.submakna += ' ' + ambil_teks_dalam_label(baku) + nomor = baku.find('sup') + if nomor: + nomor = nomor.text.strip() + self.submakna += ' [{}]'.format(nomor) + self._init_kelas(makna_label) + self.submakna = self.submakna.split('; ') self._init_contoh(makna_label) + def _init_kelas(self, makna_label): + kelas = makna_label.find(color='red') + lain = makna_label.find(color='darkgreen') + if kelas: kelas = kelas.find_all('span') + if lain: + self.kelas = {lain.text.strip(): lain['title'].strip()} + self.submakna = lain.next_sibling.strip() + self.submakna += ' ' + makna_label.find(color='grey').text.strip() + else: + self.kelas = { + k.text.strip(): k['title'].strip() for k in kelas + } if kelas else {} + def _init_contoh(self, makna_label): indeks = makna_label.text.find(': ') if indeks != -1: contoh = makna_label.text[indeks + 2:].strip() self.contoh = contoh.split('; ') else: - self.contoh = '' + self.contoh = [] def serialisasi(self): return { From 9e46e98e95e21c726c99e55c2f53b00d7f6d4328 Mon Sep 17 00:00:00 2001 From: sage Date: Sat, 16 Mar 2019 10:56:52 +0700 Subject: [PATCH 06/10] Tambahkan dokumentasi --- kbbi/__init__.py | 12 ++++- kbbi/kbbi.py | 136 +++++++++++++++++++++++++++++++++++++++++------ 2 files changed, 131 insertions(+), 17 deletions(-) diff --git a/kbbi/__init__.py b/kbbi/__init__.py index 19f79c7..40f0ee5 100644 --- a/kbbi/__init__.py +++ b/kbbi/__init__.py @@ -1,3 +1,11 @@ -from .kbbi import KBBI +""" +:mod:`kbbi` -- Modul KBBI Python +================================ + +.. module:: kbbi + :platform: Unix, Windows, Mac + :synopsis: Mengambil sebuah laman untuk kata/frasa dalam KBBI Daring. +.. moduleauthor:: sage +""" -name = 'kbbi' +from .kbbi import KBBI diff --git a/kbbi/kbbi.py b/kbbi/kbbi.py index 83a4792..42377d5 100644 --- a/kbbi/kbbi.py +++ b/kbbi/kbbi.py @@ -1,12 +1,12 @@ -''' -kbbi-python -https://kbbi.kemdikbud.go.id +""" +:mod:`kbbi` -- Modul KBBI Python +================================ -Mengambil sebuah laman untuk kata/frasa dalam KBBI Daring. - -Penggunaan: -kata = KBBI('kata') -''' +.. module:: kbbi + :platform: Unix, Windows, Mac + :synopsis: Modul ini mengandung implementasi dari modul kbbi. +.. moduleauthor:: sage +""" from urllib.parse import quote @@ -15,18 +15,23 @@ class KBBI: - ''' - Sebuah laman di KBBI. - ''' + """Sebuah laman dalam KBBI daring.""" class TidakDitemukan(Exception): - ''' + """ Galat yang menunjukkan bahwa laman tidak ditemukan dalam KBBI. - ''' + """ + def __init__(self, kata_kunci): super().__init__(kata_kunci + ' tidak ditemukan dalam KBBI!') def __init__(self, kata_kunci): + """Membuat objek KBBI baru berdasarkan kata_kunci yang diberikan. + + :param kata_kunci: Kata kunci pencarian + :type kata_kunci: str + """ + url = 'https://kbbi.kemdikbud.go.id/entri/' + quote(kata_kunci) laman = requests.get(url) @@ -38,6 +43,12 @@ def __init__(self, kata_kunci): self._init_entri(laman) def _init_entri(self, laman): + """Membuat objek-objek entri dari laman yang diambil. + + :param laman: Laman respons yang dikembalikan oleh KBBI daring. + :type laman: Response + """ + sup = BeautifulSoup(laman.text, 'html.parser') estr = '' for label in sup.find('hr').next_siblings: @@ -51,6 +62,12 @@ def _init_entri(self, laman): estr += str(label).strip() def serialisasi(self): + """Mengembalikan hasil serialisasi objek KBBI ini. + + :returns: Dictionary hasil serialisasi + :rtype: dict + """ + return { self.nama: [entri.serialisasi() for entri in self.entri] } @@ -63,7 +80,15 @@ def __repr__(self): class Entri: + """Sebuah entri dalam sebuah laman KBBI daring.""" + def __init__(self, entri_html): + """Membuat objek Entri baru berdasarkan entri_html yang diberikan. + + :param entri_html: String HTML untuk entri yang ingin diproses. + :type entri_html: str + """ + entri = BeautifulSoup(entri_html, 'html.parser') judul = entri.find('h2') dasar = judul.find_all(class_='rootword') @@ -95,8 +120,14 @@ def __init__(self, entri_html): self.makna = [Makna(m) for m in makna] def _init_kata_dasar(self, dasar): - for d in dasar: - kata = d.find('a') + """Memproses kata dasar yang ada dalam nama entri. + + :param dasar: ResultSet untuk label HTML dengan class="rootword" + :type dasar: ResultSet + """ + + for tiap in dasar: + kata = tiap.find('a') dasar_no = kata.find('sup') kata = ambil_teks_dalam_label(kata) self.kata_dasar.append( @@ -104,6 +135,12 @@ def _init_kata_dasar(self, dasar): ) def serialisasi(self): + """Mengembalikan hasil serialisasi objek Entri ini. + + :returns: Dictionary hasil serialisasi + :rtype: dict + """ + return { "nama": self.nama, "nomor": self.nomor, @@ -115,6 +152,12 @@ def serialisasi(self): } def _makna(self): + """Mengembalikan representasi string untuk semua makna entri ini. + + :returns: String representasi makna-makna + :rtype: str + """ + if (len(self.makna) > 1): return '\n'.join( str(i) + ". " + str(makna) @@ -123,12 +166,27 @@ def _makna(self): return str(self.makna[0]) def _nama(self): + """Mengembalikan representasi string untuk nama entri ini. + + :returns: String representasi nama entri + :rtype: str + """ + hasil = self.nama if self.nomor: hasil += " [{}]".format(self.nomor) if self.kata_dasar: hasil = " » ".join(self.kata_dasar) + " » " + hasil return hasil def _varian(self, varian): + """Mengembalikan representasi string untuk varian entri ini. + Dapat digunakan untuk "Varian" maupun "Bentuk tidak baku". + + :param varian: List bentuk tidak baku atau varian + :type varian: list + :returns: String representasi varian atau bentuk tidak baku + :rtype: str + """ + if varian == self.bentuk_tidak_baku: nama = "Bentuk tidak baku" elif varian == self.varian: @@ -149,7 +207,15 @@ def __repr__(self): class Makna: + """Sebuah makna dalam sebuah entri KBBI daring.""" + def __init__(self, makna_label): + """Membuat objek Makna baru berdasarkan makna_label yang diberikan. + + :param makna_label: BeautifulSoup untuk makna yang ingin diproses. + :type makna_label: BeautifulSoup + """ + self.submakna = ambil_teks_dalam_label(makna_label).rstrip(':') baku = makna_label.find('a') if baku: @@ -163,6 +229,12 @@ def __init__(self, makna_label): self._init_contoh(makna_label) def _init_kelas(self, makna_label): + """Memproses kelas kata yang ada dalam makna. + + :param makna_label: BeautifulSoup untuk makna yang ingin diproses. + :type makna_label: BeautifulSoup + """ + kelas = makna_label.find(color='red') lain = makna_label.find(color='darkgreen') if kelas: kelas = kelas.find_all('span') @@ -176,6 +248,12 @@ def _init_kelas(self, makna_label): } if kelas else {} def _init_contoh(self, makna_label): + """Memproses contoh yang ada dalam makna. + + :param makna_label: BeautifulSoup untuk makna yang ingin diproses. + :type makna_label: BeautifulSoup + """ + indeks = makna_label.text.find(': ') if indeks != -1: contoh = makna_label.text[indeks + 2:].strip() @@ -184,6 +262,12 @@ def _init_contoh(self, makna_label): self.contoh = [] def serialisasi(self): + """Mengembalikan hasil serialisasi objek Makna ini. + + :returns: Dictionary hasil serialisasi + :rtype: dict + """ + return { "kelas": self.kelas, "submakna": self.submakna, @@ -191,12 +275,27 @@ def serialisasi(self): } def _kelas(self): + """Mengembalikan representasi string untuk semua kelas kata makna ini. + + :returns: String representasi semua kelas kata + :rtype: str + """ return ' '.join("({})".format(k) for k in self.kelas) def _submakna(self): + """Mengembalikan representasi string untuk semua submakna makna ini. + + :returns: String representasi semua submakna + :rtype: str + """ return '; '.join(self.submakna) def _contoh(self): + """Mengembalikan representasi string untuk semua contoh makna ini. + + :returns: String representasi semua contoh + :rtype: str + """ return '; '.join(self.contoh) def __str__(self): @@ -210,4 +309,11 @@ def __repr__(self): def ambil_teks_dalam_label(sup): + """Mengambil semua teks dalam sup label HTML (tanpa anak-anaknya). + + :param sup: BeautifulSoup dari suatu label HTML + :type sup: BeautifulSoup + :returns: String semua teks dalam sup label HTML + :rtype: str + """ return ''.join(i.strip() for i in sup.find_all(text=True, recursive=False)) From 6cc4ed41b01c088e7dd01a0ce2c1b4b0794c2055 Mon Sep 17 00:00:00 2001 From: sage Date: Sat, 16 Mar 2019 11:00:12 +0700 Subject: [PATCH 07/10] Perbaiki gaya kode --- kbbi/kbbi.py | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/kbbi/kbbi.py b/kbbi/kbbi.py index 42377d5..609970e 100644 --- a/kbbi/kbbi.py +++ b/kbbi/kbbi.py @@ -158,7 +158,7 @@ def _makna(self): :rtype: str """ - if (len(self.makna) > 1): + if len(self.makna) > 1: return '\n'.join( str(i) + ". " + str(makna) for i, makna in enumerate(self.makna, 1) @@ -173,8 +173,10 @@ def _nama(self): """ hasil = self.nama - if self.nomor: hasil += " [{}]".format(self.nomor) - if self.kata_dasar: hasil = " » ".join(self.kata_dasar) + " » " + hasil + if self.nomor: + hasil += " [{}]".format(self.nomor) + if self.kata_dasar: + hasil = " » ".join(self.kata_dasar) + " » " + hasil return hasil def _varian(self, varian): @@ -197,9 +199,11 @@ def _varian(self, varian): def __str__(self): hasil = self._nama() - if self.pelafalan: hasil += ' ' + self.pelafalan + if self.pelafalan: + hasil += ' ' + self.pelafalan for var in (self.bentuk_tidak_baku, self.varian): - if var: hasil += '\n' + self._varian(var) + if var: + hasil += '\n' + self._varian(var) return hasil + '\n' + self._makna() def __repr__(self): @@ -237,7 +241,8 @@ def _init_kelas(self, makna_label): kelas = makna_label.find(color='red') lain = makna_label.find(color='darkgreen') - if kelas: kelas = kelas.find_all('span') + if kelas: + kelas = kelas.find_all('span') if lain: self.kelas = {lain.text.strip(): lain['title'].strip()} self.submakna = lain.next_sibling.strip() From ea2fa3fe701a78d30fce3184c34040fccd5e51cf Mon Sep 17 00:00:00 2001 From: sage Date: Sat, 16 Mar 2019 11:33:04 +0700 Subject: [PATCH 08/10] Tambahkan .gitignore --- .gitignore | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..f8c8705 --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +.vscode/ +**__pycache__/ +build/ +dist/ +kbbi.egg-info/ From a8831ecdc60f8cd64bc0d0bf21eb5554b485e3a4 Mon Sep 17 00:00:00 2001 From: sage Date: Sat, 16 Mar 2019 11:33:10 +0700 Subject: [PATCH 09/10] Perbarui README dan LICENSE --- LICENSE | 2 +- README.md | 135 +++++++++++++++++++++++++++++++++++++++++++----------- 2 files changed, 110 insertions(+), 27 deletions(-) diff --git a/LICENSE b/LICENSE index b962d7a..fdf8596 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2017 sage +Copyright (c) 2017-2019 sage Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md index ccf5ae3..bef8fc8 100644 --- a/README.md +++ b/README.md @@ -1,48 +1,131 @@ -# [WIP] kbbi-python - -Modul Python untuk mengambil entri sebuah kata/frase dalam KBBI Daring -(https://kbbi.kemdikbud.go.id). +# kbbi-python +Modul Python untuk mengambil sebuah laman untuk kata/frasa dalam [KBBI Daring][kbbi]. ## Instalasi ### Melalui pip ```bash -$ pip install kbbi +pip install kbbi ``` - ### Manual -- Clone repositori ini atau unduh kbbi.py -- Letakkan kbbi.py dalam direktori yang Anda inginkan - +1. Lakukan instalasi untuk paket-paket prasyarat ([`requests`][requests] + dan [`BeautifulSoup4`][beautifulsoup4]). +2. Klonakan repositori ini atau unduh [`kbbi.py`][kbbi-py]. +3. Letakkan `kbbi.py` dalam direktori yang Anda inginkan. ## Penggunaan -```bash -$ python -``` +Buat objek `KBBI` baru (contoh: `kata = KBBI('kata kunci')`), lalu manfaatkan +representasi `str`-nya dengan memanggil `str(kata)` atau ambil `dict` hasil +serialisasinya dengan memanggil `kata.serialisasi()`. + +Untuk lebih jelasnya, lihat contoh berikut. ```python >>> from kbbi import KBBI >>> cinta = KBBI('cinta') >>> print(cinta) +cin.ta +1. (a) suka sekali; sayang benar: orang tuaku -- kepada kami semua; -- kepada sesama makhluk +2. (a) kasih sekali; terpikat (antara laki-laki dan perempuan): sebenarnya dia tidak -- kepada lelaki itu, tetapi hanya menginginkan hartanya +3. (a) ingin sekali; berharap sekali; rindu: makin ditindas makin terasa betapa --nya akan kemerdekaan +4. (a) (kl) susah hati (khawatir); risau: tiada terperikan lagi --nya ditinggalkan ayahnya itu ``` -```text -(a) suka sekali; sayang benar -(a) kasih sekali; terpikat (antara laki-laki dan perempuan) -(a) ingin sekali; berharap sekali; rindu -(a kl) susah hati (khawatir); risau -``` + ```python ->>> for setiap in cinta.arti_contoh: ->>> print(setiap) -``` -```text -(a) suka sekali; sayang benar: orang tuaku -- kepada kami semua; -- kepada sesama makhluk -(a) kasih sekali; terpikat (antara laki-laki dan perempuan): sebenarnya dia tidak -- kepada lelaki itu, tetapi hanya menginginkan hartanya -(a) ingin sekali; berharap sekali; rindu: makin ditindas makin terasa betapa --nya akan kemerdekaan -(a kl) susah hati (khawatir); risau: tiada terperikan lagi --nya ditinggalkan ayahnya itu +>>> kata = KBBI('taksir') +>>> print(kata) +tak.sir [1] +(n) kira-kira; hitungan (kasar) + +tak.sir [2] +1. (a) (Ar) tidak mengindahkan; lalai; alpa +2. (n) (Ar) kelalaian; kealpaan +>>> import json +>>> print(json.dumps(kata.serialisasi(), indent=2)) +{ + "taksir": [ + { + "nama": "tak.sir", + "nomor": "1", + "kata_dasar": [], + "pelafalan": "", + "bentuk_tidak_baku": [], + "varian": [], + "makna": [ + { + "kelas": { + "n": "Nomina: kata benda" + }, + "submakna": [ + "kira-kira", + "hitungan (kasar)" + ], + "contoh": [] + } + ] + }, + { + "nama": "tak.sir", + "nomor": "2", + "kata_dasar": [], + "pelafalan": "", + "bentuk_tidak_baku": [], + "varian": [], + "makna": [ + { + "kelas": { + "a": "Adjektiva: kata yang menjelaskan nomina atau pronomina", + "Ar": "Arab: -" + }, + "submakna": [ + "tidak mengindahkan", + "lalai", + "alpa" + ], + "contoh": [] + }, + { + "kelas": { + "n": "Nomina: kata benda", + "Ar": "Arab: -" + }, + "submakna": [ + "kelalaian", + "kealpaan" + ], + "contoh": [] + } + ] + } + ] +} ``` + +## Lisensi + +Proyek ini didistribusikan dengan lisensi [MIT][license]. + +## Penafian + +Proyek ini merupakan proyek pribadi yang didasari oleh rasa cinta kepada +bahasa Indonesia dan bahasa pemrograman Python. Proyek ini bertujuan untuk +memudahkan akses ke KBBI daring tanpa menggunakan peramban web. Proyek ini +tidak dimaksudkan untuk menyalahi [hak cipta KBBI daring][hukum]. Proyek ini +dan pengembangnya tidak berafiliasi dengan +[Badan Bahasa Kemdikbud][badan-bahasa] maupun +[Python Software Foundation][psf]. Pengembang tidak bertanggung jawab atas +penyalahgunaan yang mungkin muncul dari proyek ini. + +[kbbi]: https://kbbi.kemdikbud.go.id +[requests]: https://pypi.org/project/requests +[beautifulsoup4]: https://pypi.org/project/requests/beautifulsoup4 +[kbbi-py]: kbbi/kbbi.py +[license]: LICENSE +[hukum]: https://kbbi.kemdikbud.go.id/Beranda/Hukum +[badan-bahasa]: http://badanbahasa.kemdikbud.go.id +[psf]: https://www.python.org/psf From 3b108dbed9f4803778da74f73f29cd95a0aaaf16 Mon Sep 17 00:00:00 2001 From: sage Date: Sat, 16 Mar 2019 11:38:23 +0700 Subject: [PATCH 10/10] Naikkan ke 0.3.0 --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index b570b8b..3373f66 100644 --- a/setup.py +++ b/setup.py @@ -9,7 +9,7 @@ setup( name='kbbi', - version='0.2.1', + version='0.3.0', description=( "A module that scraps a page in the online Indonesian dictionary (KBBI)" ),