├── MANIFEST.in ├── .gitignore ├── gnewsclient ├── data │ ├── topicMap.json │ ├── langMap.json │ └── locationMap.json ├── utils.py ├── PyOpenGraph.py └── gnewsclient.py ├── LICENSE ├── setup.py └── README.md /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include README.md LICENSE 2 | include gnewsclient/data/* 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | /dist/ 3 | /*.egg 4 | /*.egg-info 5 | .idea/ 6 | build/ 7 | venv/ 8 | -------------------------------------------------------------------------------- /gnewsclient/data/topicMap.json: -------------------------------------------------------------------------------- 1 | { 2 | "Top Stories": "TOP_STORIES", 3 | "World": "WORLD", 4 | "Nation": "NATION", 5 | "Business": "BUSINESS", 6 | "Technology": "TECHNOLOGY", 7 | "Entertainment": "ENTERTAINMENT", 8 | "Sports": "SPORTS", 9 | "Science": "SCIENCE", 10 | "Health": "HEALTH" 11 | } -------------------------------------------------------------------------------- /gnewsclient/utils.py: -------------------------------------------------------------------------------- 1 | import json 2 | import pkg_resources 3 | 4 | top_news_url = "https://news.google.com/news/rss" 5 | topic_url = "https://news.google.com/news/rss/headlines/section/topic/{}" 6 | 7 | 8 | def get_relative_path(filename): 9 | return pkg_resources.resource_filename(__name__, 'data/{}'.format(filename)) 10 | 11 | 12 | langMap = json.load(open(get_relative_path('langMap.json'), 'r')) 13 | locationMap = json.load(open(get_relative_path('locationMap.json'), 'r')) 14 | topicMap = json.load(open(get_relative_path('topicMap.json'), 'r')) 15 | -------------------------------------------------------------------------------- /gnewsclient/data/langMap.json: -------------------------------------------------------------------------------- 1 | { 2 | "english": "en", 3 | "indonesian": "id", 4 | "czech": "cs", 5 | "german": "de", 6 | "spanish": "es-419", 7 | "french": "fr", 8 | "italian": "it", 9 | "latvian": "lv", 10 | "lithuanian": "lt", 11 | "hungarian": "hu", 12 | "dutch": "nl", 13 | "norwegian": "no", 14 | "polish": "pl", 15 | "portuguese brasil": "pt-419", 16 | "portuguese portugal": "pt-150", 17 | "romanian": "ro", 18 | "slovak": "sk", 19 | "slovenian": "sl", 20 | "swedish": "sv", 21 | "vietnamese": "vi", 22 | "turkish": "tr", 23 | "greek": "el", 24 | "bulgarian": "bg", 25 | "russian": "ru", 26 | "serbian": "sr", 27 | "ukrainian": "uk", 28 | "hebrew": "he", 29 | "arabic": "ar", 30 | "marathi": "mr", 31 | "hindi": "hi", 32 | "bengali": "bn", 33 | "tamil": "ta", 34 | "telugu": "te", 35 | "malyalam": "ml", 36 | "thai": "th", 37 | "chinese simplified": "zh-Hans", 38 | "chinese traditional": "zh-Hant", 39 | "japanese": "ja", 40 | "korean": "ko" 41 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Nikhil Kumar Singh 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup 2 | 3 | 4 | def readme(): 5 | with open('README.md') as f: 6 | descr = f.read() 7 | return descr 8 | 9 | 10 | setup(name='gnewsclient', 11 | version='1.12', 12 | classifiers=[ 13 | "License :: OSI Approved :: MIT License", 14 | "Programming Language :: Python :: 3", 15 | "Programming Language :: Python :: 3.7", 16 | ], 17 | keywords='google news feed python client feed', 18 | description='Python client for Google News Feed.', 19 | long_description=readme(), 20 | long_description_content_type="text/markdown", 21 | url='http://github.com/nikhilkumarsingh/gnewsclient', 22 | author='Nikhil Kumar Singh', 23 | author_email='nikhilksingh97@gmail.com', 24 | license='MIT', 25 | packages=['gnewsclient'], 26 | install_requires=['requests', 'fuzzywuzzy', 'feedparser'], 27 | include_package_data=True, 28 | zip_safe=False, 29 | entry_points={ 30 | 'console_scripts': [ 31 | 'gnewsclient = gnewsclient.gnewsclient:main' 32 | ], 33 | } 34 | ) 35 | -------------------------------------------------------------------------------- /gnewsclient/data/locationMap.json: -------------------------------------------------------------------------------- 1 | { 2 | "Australia": "AU", 3 | "Botswana": "BW", 4 | "Canada ": "CA", 5 | "Ethiopia": "ET", 6 | "Ghana": "GH", 7 | "India ": "IN", 8 | "Indonesia": "ID", 9 | "Ireland": "IE", 10 | "Israel ": "IL", 11 | "Kenya": "KE", 12 | "Latvia": "LV", 13 | "Malaysia": "MY", 14 | "Namibia": "NA", 15 | "New Zealand": "NZ", 16 | "Nigeria": "NG", 17 | "Pakistan": "PK", 18 | "Philippines": "PH", 19 | "Singapore": "SG", 20 | "South Africa": "ZA", 21 | "Tanzania": "TZ", 22 | "Uganda": "UG", 23 | "United Kingdom": "GB", 24 | "United States": "US", 25 | "Zimbabwe": "ZW", 26 | "Czech Republic": "CZ", 27 | "Germany": "DE", 28 | "Austria": "AT", 29 | "Switzerland": "CH", 30 | "Argentina": "AR", 31 | "Chile": "CL", 32 | "Colombia": "CO", 33 | "Cuba": "CU", 34 | "Mexico": "MX", 35 | "Peru": "PE", 36 | "Venezuela": "VE", 37 | "Belgium ": "BE", 38 | "France": "FR", 39 | "Morocco": "MA", 40 | "Senegal": "SN", 41 | "Italy": "IT", 42 | "Lithuania": "LT", 43 | "Hungary": "HU", 44 | "Netherlands": "NL", 45 | "Norway": "NO", 46 | "Poland": "PL", 47 | "Brazil": "BR", 48 | "Portugal": "PT", 49 | "Romania": "RO", 50 | "Slovakia": "SK", 51 | "Slovenia": "SI", 52 | "Sweden": "SE", 53 | "Vietnam": "VN", 54 | "Turkey": "TR", 55 | "Greece": "GR", 56 | "Bulgaria": "BG", 57 | "Russia": "RU", 58 | "Ukraine ": "UA", 59 | "Serbia": "RS", 60 | "United Arab Emirates": "AE", 61 | "Saudi Arabia": "SA", 62 | "Lebanon": "LB", 63 | "Egypt": "EG", 64 | "Bangladesh": "BD", 65 | "Thailand": "TH", 66 | "China": "CN", 67 | "Taiwan": "TW", 68 | "Hong Kong": "HK", 69 | "Japan": "JP", 70 | "Republic of Korea": "KR" 71 | } -------------------------------------------------------------------------------- /gnewsclient/PyOpenGraph.py: -------------------------------------------------------------------------------- 1 | from html.parser import HTMLParser 2 | from typing import Dict 3 | 4 | import requests 5 | 6 | 7 | class PyOpenGraph(object): 8 | 9 | def __init__(self, url): 10 | try: 11 | html = requests.get(url).text 12 | except: 13 | html = "" 14 | p = PyOpenGraphParser() 15 | p.feed(html) 16 | 17 | if p.properties['image'] and p.properties['image:height'] and p.properties['image:width']: 18 | ratio = int(p.properties['image:height']) / int(p.properties['image:width']) 19 | p.properties['image_width'] = 150 20 | p.properties['image_height'] = 150 * ratio 21 | else: 22 | p.properties['image_width'] = 150 23 | p.properties['image_height'] = 150 24 | p.properties.pop('image:height', None) 25 | p.properties.pop('image:width', None) 26 | self.properties = p.properties 27 | p.close() 28 | 29 | def __str__(self): 30 | return self.properties['title'] 31 | 32 | 33 | class PyOpenGraphParser(HTMLParser): 34 | 35 | def __init__(self): 36 | HTMLParser.__init__(self) 37 | self.properties = {'url': None, 'site_name': None, 'title': None, 'description': None, 38 | 'image': None, 'image:height': None, 'image:width': None} 39 | 40 | def handle_starttag(self, tag, attrs): 41 | if tag == 'meta': 42 | attrdict = dict(attrs) 43 | if 'property' in attrdict and attrdict['property'].startswith('og:') and 'content' in attrdict: 44 | self.properties[attrdict['property'].replace('og:', '')] = attrdict['content'] 45 | 46 | def handle_endtag(self, tag): 47 | pass 48 | 49 | def error(self, msg): 50 | pass 51 | -------------------------------------------------------------------------------- /gnewsclient/gnewsclient.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | 3 | import feedparser 4 | import requests 5 | from fuzzywuzzy import process 6 | 7 | from .PyOpenGraph import PyOpenGraph 8 | from .utils import locationMap, langMap, topicMap, top_news_url, topic_url 9 | 10 | 11 | class NewsClient: 12 | 13 | def __init__(self, location='United States', language='english', topic='Top Stories', 14 | use_opengraph=False, max_results=10): 15 | """ 16 | client initialization 17 | """ 18 | # list of available locations, languages and topics 19 | self.locations = list(locationMap) 20 | self.languages = list(langMap) 21 | self.topics = list(topicMap) 22 | 23 | # setting initial configuration 24 | self.location = location 25 | self.language = language 26 | self.topic = topic 27 | 28 | # other settings 29 | self.use_opengraph = use_opengraph 30 | self.max_results = max_results 31 | 32 | def get_config(self): 33 | """ 34 | function to get current configuration 35 | """ 36 | config = { 37 | 'location': self.location, 38 | 'language': self.language, 39 | 'topic': self.topic, 40 | } 41 | return config 42 | 43 | @property 44 | def params_dict(self): 45 | """ 46 | function to get params dict for HTTP request 47 | """ 48 | location_code = 'US' 49 | language_code = 'en' 50 | if len(self.location): 51 | location_code = locationMap[process.extractOne(self.location, self.locations)[0]] 52 | if len(self.language): 53 | language_code = langMap[process.extractOne(self.language, self.languages)[0]] 54 | params = { 55 | 'hl': language_code, 56 | 'gl': location_code, 57 | 'ceid': '{}:{}'.format(location_code, language_code) 58 | } 59 | return params 60 | 61 | def get_news(self): 62 | """ 63 | function to get news articles 64 | """ 65 | if self.topic is None or self.topic == 'Top Stories': 66 | resp = requests.get(top_news_url, params=self.params_dict) 67 | else: 68 | topic_code = topicMap[process.extractOne(self.topic, self.topics)[0]] 69 | resp = requests.get(topic_url.format(topic_code), params=self.params_dict) 70 | return self.parse_feed(resp.content) 71 | 72 | def print_news(self): 73 | articles = self.get_news() 74 | for article in articles: 75 | print(article['title']) 76 | print(article['link'], end='\n\n') 77 | 78 | def parse_feed(self, content): 79 | """ 80 | utility function to parse feed 81 | """ 82 | feed = feedparser.parse(content) 83 | articles = [] 84 | for entry in feed['entries'][:self.max_results]: 85 | article = { 86 | 'title': entry['title'], 87 | 'link': entry['link'] 88 | } 89 | try: 90 | article['media'] = entry['media_content'][0]['url'] 91 | except KeyError: 92 | article['media'] = None 93 | if self.use_opengraph: 94 | article = {**PyOpenGraph(article['link']).properties, **article} 95 | articles.append(article) 96 | return articles 97 | 98 | 99 | def main(): 100 | parser = argparse.ArgumentParser(description="GoogleNews Client CLI!") 101 | 102 | parser.add_argument("-loc", "--location", type=str, default='United States', 103 | help="Set news location.") 104 | 105 | parser.add_argument("-lang", "--language", type=str, default='english', 106 | help="Set news language.") 107 | 108 | parser.add_argument("-t", "--topic", type=str, default='Top Stories', 109 | help="Set news topic.") 110 | 111 | parser.add_argument("-sloc", "--show-locations", action='store_true', 112 | help="Show location choices") 113 | 114 | parser.add_argument("-slang", "--show-languages", action='store_true', 115 | help="Show language choices") 116 | 117 | parser.add_argument("-st", "--show-topics", action='store_true', 118 | help="Show topic choices") 119 | 120 | args = parser.parse_args() 121 | args_dict = vars(args) 122 | 123 | if args_dict['show_locations']: 124 | print("\n".join(locationMap.keys())) 125 | 126 | elif args_dict['show_languages']: 127 | print("\n".join(langMap.keys())) 128 | 129 | elif args_dict['show_topics']: 130 | print("\n".join(topicMap.keys())) 131 | 132 | client = NewsClient(location=args_dict['location'], language=args_dict['language'], topic=args_dict['topic']) 133 | client.print_news() 134 | 135 | 136 | if __name__ == "__main__": 137 | main() 138 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![PyPI](https://img.shields.io/badge/PyPi-v1.12-f39f37.svg)](https://pypi.python.org/pypi/gnewsclient) 2 | [![license](https://img.shields.io/github/license/mashape/apistatus.svg?maxAge=2592000)](https://github.com/nikhilkumarsingh/gnewsclient/blob/master/LICENSE.txt) 3 | 4 | # gnewsclient 5 | 6 | An easy-to-use python client for [Google News feeds](https://news.google.com/). 7 | 8 | ## Installation 9 | 10 | To install gnewsclient, simply, 11 | ``` 12 | $ pip install gnewsclient 13 | ``` 14 | 15 | ## Usage 16 | 17 | - Create a NewsClient object: 18 | ```python 19 | >>> from gnewsclient import gnewsclient 20 | >>> client = gnewsclient.NewsClient(language='hindi', location='india', topic='Business', max_results=3) 21 | ``` 22 | 23 | - Get current parameter settings 24 | ```python 25 | >>> client.get_config() 26 | {'location': 'india', 'language': 'hindi', 'topic': 'Sorts'} 27 | ``` 28 | 29 | - Get news feed 30 | ```python 31 | >>> client.get_news() 32 | [{'title': 'शेयर बाजार/ सेंसेक्स 490 अंक की बढ़त के साथ 39055 पर, निफ्टी 150 प्वाइंट ऊपर 11726 पर बंद - दैनिक भास्कर', 33 | 'link': 'https://www.bhaskar.com/national/news/stock-market-sensex-jumps-500-points-nifty-up-150-points-on-wednesday-24-april-01529551.html', 34 | 'media': None}, 35 | {'title': 'Reliance Jio की सेवाएं हो सकती हैं महंगी! यह है वजह - Jansatta', 36 | 'link': 'https://www.jansatta.com/business/reliance-jio-planning-to-hike-in-prices-to-invest-9000-crore-in-capacity-lease-deals/990616/', 37 | 'media': None}, 38 | {'title': 'डील/ एस्सेल प्रोपैक की कंट्रोलिंग हिस्सेदारी 3211 करोड़ रुपए में खरीदेगी ब्लैकस्टोन - दैनिक भास्कर', 39 | 'link': 'https://www.bhaskar.com/national/news/blackstone-snaps-up-essel-propack-for-rs-3211-cr-01528505.html', 40 | 'media': 'https://lh3.googleusercontent.com/proxy/XgI0fJc8QD0syNzTzYgSyerob_9QSyKEIKZAdGecQ-C4u5HxjqcW-HrpuglbWj8CoxLDcrxOzT7QS7GNJxGv6kt6cviIzKpsaGpsr4qEwpyc=-w150-h150-c'}, 41 | {'title': 'मारुति सुजुकी की बलेनो हुई स्मार्ट, जानें कीमत और खास फीचर्स - Hindustan', 42 | 'link': 'https://www.livehindustan.com/business/story-maruti-suzuki-launched-new-smart-baleno-know-price-and-features-2500723.html', 43 | 'media': 'https://lh6.googleusercontent.com/proxy/Jwm-p9YBF5bT3bcsXv5KGn_83nniRJsi9CArg1yU27LrKMu72cl1ekX4na_e9JfjhWHrRKD-LWLdUiK1H91VnB_gwVhoJNQX_AvhLaKUId-uodvOMDIe=-w150-h150-c'}] 44 | ``` 45 | 46 | - Get news feed with OpenGraph data 47 | ```python 48 | >>> client = gnewsclient.NewsClient(language='hindi', location='india', topic='Business', use_opengraph=True, max_results=5) 49 | [{'url': 'https://aajtak.intoday.in/story/share-market-low-sensex-nifty-bse-nse-yesbank-ntpc-heromotoco-rupee-tut-1-1090122.html', 50 | 'site_name': 'aajtak.intoday.in', 51 | 'title': 'लाल निशान पर बंद हुआ शेयर बाजार, अब भी 40 हजार के पार - आज तक', 52 | 'description': 'सप्ताह के दूसरे कारोबारी दिन भारतीय शेयर बाजार ने एक मुकाम को हासिल किया. हालांकि कारोबार के अंत में सेंसेक्स और निफ्टी लाल निशान पर बंद हुए.', 53 | 'image': 'https://smedia2.intoday.in/aajtak/images/stories/062019/sensex_1559643004_618x347.jpeg', 54 | 'type': 'Article', 55 | 'image_width': 150, 56 | 'image_height': 150, 57 | 'link': 'https://aajtak.intoday.in/story/share-market-low-sensex-nifty-bse-nse-yesbank-ntpc-heromotoco-rupee-tut-1-1090122.html', 58 | 'media': None}, 59 | {'url': 'https://www.amarujala.com/photo-gallery/automobiles/bajaj-platina-110-h-gear-vs-tvs-victor-full-comparison-and-price', 60 | 'site_name': 'Amar Ujala', 61 | 'title': 'Bajaj Platina 110 Vs TVS Victor, जानिये कौन सी बाइक है वैल्यू फॉर मनी - अमर उजाला', 62 | 'description': 'बजाज ऑटो ने भारत में अपनी नई Platina 110 H-Gear को लांच कर दिया है। नई Platina 110 का सीधा मुकाबला TVS की Victor से होगा। चाइये जानते हैं इन दोनों', 63 | 'image': 'https://spiderimg.amarujala.com/assets/images/2019/06/05/750x506/bajaj-platina-110-vs-tvs-victor_1559719613.jpeg', 64 | 'locale': 'hi_IN', 65 | 'type': 'article', 66 | 'headline': 'Bajaj Platina 110 H-Gear 110 या TVS Victor, जानिये कौन सी बाइक है सबसे बेहतर', 67 | 'image_width': 150, 68 | 'image_height': 101.19999999999999, 69 | 'link': 'https://www.amarujala.com/photo-gallery/automobiles/bajaj-platina-110-h-gear-vs-tvs-victor-full-comparison-and-price', 70 | 'media': None}] 71 | ``` 72 | 73 | - Changing parameters 74 | ```python 75 | >>> client.location = 'india' 76 | >>> client.language = 'hindi' 77 | >>> client.topic = 'Sports' 78 | >>> client.get_news() 79 | [{'title': 'जब डिविलियर्स ने एक हाथ से मारा शॉट, ग्राउंड के पार पहुंची गेंद - आज तक', 80 | 'link': 'https://aajtak.intoday.in/sports/story/rcb-vs-kxip-ab-de-villiers-one-handed-six-out-of-the-ground-tspo-1-1078680.html', 81 | 'media': None}, 82 | {'title': 'हितों का टकराव: बीसीसीआई लोकपाल ने सचिन तेंडुलकर और वीवीएस लक्ष्मण को नोटिस जारी किया - Navbharat Times', 83 | 'link': 'https://navbharattimes.indiatimes.com/sports/cricket/iplt20/news/ombudsman-notice-to-sachin-tendulkar-vvs-laxman-cricketers-doing-voluntary-service/articleshow/69031167.cms', 84 | 'media': None}, 85 | {'title': 'आंद्रे रसेल और गेल को मिला आईपीएल का ईनाम, वेस्टइंडीज की विश्वकप 2019 टीम में मिली जगह - India TV हिंदी', 86 | 'link': 'https://hindi.indiatvnews.com/sports/cricket-world-cup-2019-westindies-team-squad-2019-chris-gayle-and-andre-russell-got-place-633917', 87 | 'media': None}, 88 | {'title': 'Macth Update, IPL 2019, RCB vs KXIP: पंजाब को हराकर बैंगलोर ने लगाई जीत की हैट्रिक, प्लेऑफ की दौड़ में बरकरार - Times Now Hindi', 89 | 'link': 'https://hindi.timesnownews.com/cricket/article/ipl-live-score-rcb-vs-kxip-royal-challengers-bangalore-vs-kings-xi-punjab-m-chinnaswamy-stadium-ipl-2019-live-match-score-in-hindi/406206', 90 | 'media': None} 91 | ] 92 | ``` 93 | 94 | - Get list of available locations, languages and topics 95 | ```python 96 | >>> client.locations 97 | ['Australia', 'Botswana', 'Canada ', 'Ethiopia', 'Ghana', 'India ', 'Indonesia', 'Ireland', 'Israel ', 'Kenya', 'Latvia', 98 | 'Malaysia', 'Namibia', 'New Zealand', 'Nigeria', 'Pakistan', 'Philippines', 'Singapore', 'South Africa', 'Tanzania', 'Uganda', 99 | 'United Kingdom', 'United States', 'Zimbabwe', 'Czech Republic', 'Germany', 'Austria', 'Switzerland', 'Argentina', 'Chile', 100 | 'Colombia', 'Cuba', 'Mexico', 'Peru', 'Venezuela', 'Belgium ', 'France', 'Morocco', 'Senegal', 'Italy', 'Lithuania', 101 | 'Hungary', 'Netherlands', 'Norway', 'Poland', 'Brazil', 'Portugal', 'Romania', 'Slovakia', 'Slovenia', 'Sweden', 'Vietnam', 102 | 'Turkey', 'Greece', 'Bulgaria', 'Russia', 'Ukraine ', 'Serbia', 'United Arab Emirates', 'Saudi Arabia', 'Lebanon', 'Egypt', 103 | 'Bangladesh', 'Thailand', 'China', 'Taiwan', 'Hong Kong', 'Japan', 'Republic of Korea'] 104 | >>> client.languages 105 | ['english', 'indonesian', 'czech', 'german', 'spanish', 'french', 'italian', 'latvian', 'lithuanian', 'hungarian', 'dutch', 106 | 'norwegian', 'polish', 'portuguese brasil', 'portuguese portugal', 'romanian', 'slovak', 'slovenian', 'swedish', 'vietnamese', 107 | 'turkish', 'greek', 'bulgarian', 'russian', 'serbian', 'ukrainian', 'hebrew', 'arabic', 'marathi', 'hindi', 'bengali', 'tamil', 108 | 'telugu', 'malyalam', 'thai', 'chinese simplified', 'chinese traditional', 'japanese', 'korean'] 109 | >>> client.topics 110 | ['Top Stories', 111 | 'World', 112 | 'Nation', 113 | 'Business', 114 | 'Technology', 115 | 'Entertainment', 116 | 'Sports', 117 | 'Science', 118 | 'Health'] 119 | ``` --------------------------------------------------------------------------------