Optimization and restructuring
Function's responsibility
The initial approach makes the read_page function depend on both requests and BeautifulSoup modules (though BeautifulSoup functionality/features is not actually used there). Then, a soup instance is passed to news_scraper(soup) function.
To reduce dependencies let read_page function extract the remote webpage and just return its contents r.content. That will also uncouple news_scraper from soup instance arguments and allow to pass any markup content, making the function more unified.
Namings
BASE = "https://www.pravda.com.ua" within news_scraper function is essentially acting like a local variable. But considering it as a constant - it should be moved out at top level and renamed to a meaningful BASE_URL = "https://www.pravda.com.ua".
i is not a good variable name to reflect a document element in for i in soup.select("div.news.news_all > div"). Good names are node, el, atricle ...
The main function is better renamed to news_to_df to reflect the actual intention.
main(START_URL) - don't give arguments uppercased names, it should be start_url.
Parsing news items and composing "date" value
As you parse webpages (html pages) - specifying html.parser or html5lib (not lxml) is preferable for creating BeautifulSoup instance.
Extracting an article publication time with generic i.div.text would be wrong as a parent node div.article could potentially contain another child div nodes with text content. Therefore, the search query should be more exact: news_time = el.find(class_='article__time').text.
Instead of assigning, traversing and dropping "time" column and aggregating:
dataframe["date"] = (
dataframe["link"]
.str.extract("(\d{4}/\d{2}/\d{2})")[0]
.str.cat(dataframe["time"], sep=" ")
)
- that all can be eliminated and the date column can be calculated at once by combining the extracted date value (powered by precompiled regex pattern DATE_PAT = re.compile(r'\d{4}/\d{2}/\d{2}')) and news_time value.
Instead of collecting a list of lists - a more robust way is to collect a list of dictionaries like {'title': ..., 'subtitle': ..., 'date': ..., 'link': ...} as that will prevent confusing the order of values for strict list of column names.
Furthermore, instead of appending to list, a sequence of needed dictionaries can be efficiently collected with generator function. See the full implementation below.
The main function (new name: news_to_df)
The while next_page: turned to while True:.
except: - do not use bare except, at least catch basic Exception class: except Exception:.
The repeated blocks of read_page, news_scraper and results.append(df) statements can be reduced to a single block (see below).
One subtle nuance is that the ultimate "next" page will have '/archives/' in its a.button.button_next.href path, signaling the end of paging. It's worth to handle that situation explicitly:
if next_page == '/archives/':
break
The final optimized solution:
import requests
import pandas as pd
from bs4 import BeautifulSoup
from time import sleep
import re
BASE_URL = "https://www.pravda.com.ua"
DATE_PAT = re.compile(r'\d{4}/\d{2}/\d{2}')
def read_page(url):
r = requests.get(url)
return r.content
def _collect_newsitems_gen(articles):
for el in articles:
a_node = el.a
news_time = el.find(class_='article__time').text
yield {'title': a_node.text,
'subtitle': el.find(class_="article__subtitle").text,
'date': f'{DATE_PAT.search(a_node["href"]).group()} {news_time}',
'link': f'{BASE_URL}{a_node["href"]}'}
def news_scraper(news_content):
soup = BeautifulSoup(news_content, "html5lib")
articles = soup.select("div.news.news_all > div")
next_page_url = soup.select_one("div.archive-navigation > a.button.button_next")["href"]
df = pd.DataFrame(list(_collect_newsitems_gen(articles)),
columns=["title", "subtitle", "date", "link"])
return df, f'{BASE_URL}{next_page_url}'
def news_to_df(start_url):
next_page = start_url
results = []
while True:
print(next_page)
try:
content = read_page(next_page)
df, next_page = news_scraper(content)
results.append(df)
if next_page == '/archives/':
break
except Exception:
break
sleep(1)
return pd.concat([r for r in results], ignore_index=True)
if __name__ == "__main__":
df = news_to_df("https://www.pravda.com.ua/archives/date_24122019/")
assert df.shape == (120, 4) # it's true as of today, 12.26.2019
If printing the final resulting df with print(df.to_string()) - the output would look like below (with cutted the middle part to make it a bit shorter):
https://www.pravda.com.ua/archives/date_24122019/
https://www.pravda.com.ua/archives/date_25122019/
https://www.pravda.com.ua/archives/
title subtitle date link
0 Голова Закарпаття не зрозумів, за що його звіл... Голова Закарпатської обласної державної адміні... 2019/12/24 23:36 https://www.pravda.com.ua/news/2019/12/24/7235...
1 Стало відомо коли відновлять будівництво об'єк... На зустрічі представників керівництва ХК Київм... 2019/12/24 22:41 https://www.pravda.com.uahttps://www.epravda.c...
2 ВАКС продовжив арешт Гримчаку до 14 лютого Вищий антикорупційний продовжив арешт для коли... 2019/12/24 22:25 https://www.pravda.com.ua/news/2019/12/24/7235...
3 Економічні новини 24 грудня: транзит газу, зни... Про транзит газу, про зниження "платіжок", про... 2019/12/24 22:10 https://www.pravda.com.uahttps://www.epravda.c...
4 Трамп: США готові до будь-якого "різдвяного по... Президент США Дональд Трамп на тлі побоювань щ... 2019/12/24 22:00 https://www.pravda.com.uahttps://www.eurointeg...
5 У податковій слідчі дії – електронні сервіси п... Державна податкова служба попереджає, що елект... 2019/12/24 21:55 https://www.pravda.com.ua/news/2019/12/24/7235...
6 Мінфін знизив ставки за держборгом до 11% річних Міністерство фінансів знизило середньозважену ... 2019/12/24 21:31 https://www.pravda.com.uahttps://www.epravda.c...
7 Україна викреслила зі списку на обмін ексберку... Російський адвокат Валентин Рибін заявляє, що ... 2019/12/24 21:13 https://www.pravda.com.ua/news/2019/12/24/7235...
8 Посол: іспанський клуб покарають за образи укр... Посол України в Іспанії Анатолій Щерба заявив,... 2019/12/24 20:45 https://www.pravda.com.uahttps://www.eurointeg...
9 Міністр енергетики: "Газпром" може "зістрибнут... У Міністерстві енергетики не виключають, що "Г... 2019/12/24 20:03 https://www.pravda.com.uahttps://www.epravda.c...
10 Зеленський призначив Арахамію секретарем Націн... Президент Володимир Зеленський затвердив персо... 2019/12/24 20:00 https://www.pravda.com.ua/news/2019/12/24/7235...
...
110 Уряд придумав, як захистити українців від шкод... Кабінет міністрів схвалив законопроєкт, який з... 2019/12/25 06:54 https://www.pravda.com.ua/news/2019/12/25/7235...
111 Кіберполіція та YouControl домовилися про спів... Кіберполіція та компанія YouControl підписали ... 2019/12/25 06:00 https://www.pravda.com.ua/news/2019/12/25/7235...
112 В окупованому Криму продають прикарпатські яли... У центрі Сімферополя, на новорічному ярмарку п... 2019/12/25 05:11 https://www.pravda.com.ua/news/2019/12/25/7235...
113 У США схожий на Санту чоловік пограбував банк,... У Сполучених Штатах чоловік з білою, як у Сант... 2019/12/25 04:00 https://www.pravda.com.ua/news/2019/12/25/7235...
114 У Росії за "дитячу порнографію" посадили блоге... Верховний суд російської Чувашії засудив до тр... 2019/12/25 03:26 https://www.pravda.com.ua/news/2019/12/25/7235...
115 Уряд провів екстрене засідання через газові пе... Кабінет міністрів у вівторок ввечері провів ек... 2019/12/25 02:31 https://www.pravda.com.ua/news/2019/12/25/7235...
116 Нова стратегія Мінспорту: розвиток інфраструкт... Стратегія розвитку спорту і фізичної активност... 2019/12/25 02:14 https://www.pravda.com.ua/news/2019/12/25/7235...
117 Милованов розкритикував НБУ за курс гривні та ... Міністр розвитку економіки Тимофій Милованов р... 2019/12/24 01:46 https://www.pravda.com.uahttps://www.epravda.c...
118 Російські літаки розбомбили школу в Сирії: заг... Щонайменше 10 людей, в тому числі шестеро – ді... 2019/12/25 01:04 https://www.pravda.com.ua/news/2019/12/25/7235...
119 Ліквідація "майданчиків Яценка": Зеленський пі... Президент Володимир Зеленський підписав закон,... 2019/12/25 00:27 https://www.pravda.com.ua/news/2019/12/25/7235...
P.S. From Ukraine with love ...
sleep(1)in the for loop? \$\endgroup\$<a href="/archives/" class="button button_next">...</a>link, that assigns/archives/tonext_page. How are you handling that case? \$\endgroup\$archivesI didn't think it all through. Since/archiveswould throw an error if I tried to scrape it withnews_scraperfunction, I thought I'd just stop at that point and setnext_pagetoFalseto quitwhileloop \$\endgroup\$