Web-Scraping All Text Between <table>TABLE I NEED</table> in Python

Viewed 86

I'm attempting to scrape from the following URL to get CoVid data from WorldOMeter, and on this page exists a table with the id: main_table_countries_today which contains 15x225 (3,375) cells of data I wish to gather.

I've tried a few methods, but let me share the closest attempt I think I've made:

import requests
from os import system

url = 'https://www.worldometers.info/coronavirus/'
table_id = 'main_table_countries_today'
table_end = '</table>'


# Refreshes the Terminal Emulator window
def clear_screen():

    def bash_input(user_in):
        _ = system(user_in)
    
    bash_input('clear')


# This bot searches for <table> and </table> to start/stop recording data
class Bot:

    def __init__(self,
                 line_added=False,
                 looking_for_start=True,
                 looking_for_end=False):

        self.line_adding = line_added
        self.looking_for_start = looking_for_start
        self.looking_for_end = looking_for_end
    
    def set_line_adding(self, bool):

        self.line_adding = bool

    def set_start_look(self, bool):

        self. looking_for_start = bool

    def set_end_look(self, bool):

        self.looking_for_end = bool


if __name__ == '__main__':

    # Start with a fresh Terminal emulator
    clear_screen()
    
    my_bot = Bot()

    r = requests.get(url).text
    all_r = r.split('\n')

    for rs in all_r:

        if my_bot.looking_for_start and table_id in rs:
                
            my_bot.set_line_adding(True)
            my_bot.set_end_look(True)
            my_bot.set_start_look(False)
        
        if my_bot.looking_for_end and table_end in rs:    
                
            my_bot.set_line_adding(False)
            my_bot.looking_for_end(False)
        
        if my_bot.line_adding:

            all_lines.append(rs)
        

        for lines in all_lines:
            print(lines)
        
        print('\n\n\n\n')
        print(len(all_lines))

This prints 6,551 lines of code, which is over twice what I need. This would be okay normally, as the next step would be to clean the lines that are irrelevant to my data, however, this does not yield the whole table. Another previous attempt I made with BeautifulSoup (very similar process) that also did not start and stop with the aforementioned table. It looked like this:

from bs4 import BeautifulSoup
import requests
from os import system

url = 'https://www.worldometers.info/coronavirus/'
table_id = 'main_table_countries_today'
table_end = '</table>'

# Declare an empty list to fill with lines of text
all_lines = list()


if __name__ == '__main__':

    # Here we go, again...
    _ = system('clear')

    r = requests.get(url).text
    soup = BeautifulSoup(r)
    my_table = soup.find_all('table', {'id': table_id})

    for current_line in my_table:

        page_lines = str(current_line).split('\n')

        for line in page_lines:
            all_lines.append(line)

    for line in all_lines:
        print(line)

    print('\n\n')
    print(len(all_lines))

yielding 5,547 lines as a result.

I've also made an attempt with Pandas and Selenium, but I've since scraped that code. I'm hoping by showing my two 'best' attempts, someone might see some glaring problem that I'm missing.

If I can get the data on the screen, I'd be happy with that. I'm ultimately trying to convert this data into a dictionary (that will be exported as a .json file) that looks something like this:

data = {
    "Country": [country for country in countries],
    "Total Cases": [case for case in total_cases],
    "New Cases": [case for case in new_cases],
    "Total Deaths": [death for death in total_deaths],
    "New Deaths": [death for death in new_deaths],
    "Total Recovered": [death for death in total_recovered],
    "New Recovered": [death for death in new_recovered],
    "Active Cases": [case for case in active_cases],
    "Serious/Critical": [case for case in serious_critical],
    "Total Cases/1M pop": [case for case in total_case_per_million],
    "Deaths/1M pop": [death for death in deaths_per_million],
    "Total Tests": [test for test in total_tests],
    "Tests/1M pop": [test for test in tests_per_million],
    "Population": [population for population in populations]
}

Any advice?

2 Answers

The table contains lots of other information. You can get first 15 <td> cells in a row and strip first/last 8 rows:

import requests
import pandas as pd
from bs4 import BeautifulSoup


url = "https://www.worldometers.info/coronavirus/"
soup = BeautifulSoup(requests.get(url).content, "html.parser")

all_data = []
for tr in soup.select("#main_table_countries_today tr:has(td)")[8:-8]:
    tds = [td.get_text(strip=True) for td in tr.select("td")][:15]
    all_data.append(tds)

df = pd.DataFrame(
    all_data,
    columns=[
        "#",
        "Country",
        "Total Cases",
        "New Cases",
        "Total Deaths",
        "New Deaths",
        "Total Recovered",
        "New Recovered",
        "Active Cases",
        "Serious, Critical",
        "Tot Cases/1M pop",
        "Deaths/1M pop",
        "Total Tests",
        "Tests/1M pop",
        "Population",
    ],
)
print(df)

Prints:

       #                 Country Total Cases New Cases Total Deaths New Deaths Total Recovered New Recovered Active Cases Serious, Critical Tot Cases/1M pop Deaths/1M pop  Total Tests Tests/1M pop     Population
0      1                     USA  35,745,024                629,315                 29,666,117                  5,449,592            11,516          107,311         1,889  529,679,820    1,590,160    333,098,437
1      2                   India  31,693,625   +39,041      424,777       +393      30,846,509       +33,636      422,339             8,944           22,725           305  468,216,510      335,725  1,394,642,466
2      3                  Brazil  19,917,855                556,437                 18,619,542                    741,876             8,318           92,991         2,598   55,034,721      256,943    214,190,490
3      4                  Russia   6,288,677   +22,804      159,352       +789       5,625,890       +17,271      503,435             2,300           43,073         1,091  165,800,000    1,135,600    146,002,094

...

218  219                   Samoa           3                                                 3                          0                                 15                                                199,837
219  220            Saint Helena           2                                                 2                          0                                328                                                  6,097
220  221              Micronesia           1                                                 1                          0                                  9                                                116,324
221  222                   China      93,005       +75        4,636                     87,347           +24        1,022                25               65             3  160,000,000      111,163  1,439,323,776

Here's what you can try, you can find basic explanation inside code :

from bs4 import BeautifulSoup
import requests
import pandas as pd

page = requests.get('https://www.worldometers.info/coronavirus')
soup = BeautifulSoup(page.content,"lxml")

table = soup.find('table', attrs={'id': 'main_table_countries_today'})
# Finding table using id

trs = table.find_all("tr", attrs={"style": ""})
# Finding tr from table using style attribute

data = []
data.append(trs[0].text.strip().split("\n")[:13])
# Appending first element of trs to data(list)

for tr in trs[1:]:
    data.append(tr.text.strip().split("\n")[:12])
    # Appending all other data from tr in data(list)

df = pd.DataFrame(data[1:], columns=data[0][:12])
# Converting data into pandas DataFrame and specifying header name from first row of data.

print(df)
"""
          #          Country,Other  TotalCases   NewCases TotalDeaths  \
0     World            198,878,345    +370,787  4,238,503      +6,065
1         1                    USA  35,745,024               629,315
2         2                  India  31,693,625    +39,041    424,777
3         3                 Brazil  19,917,855               556,437
4         4                 Russia   6,288,677    +22,804    159,352
..      ...                    ...         ...        ...         ...
208     211  Saint Pierre Miquelon          28
209     213             Montserrat          21                     1
210     215         Western Sahara          10                     1
211     222                  China      93,005        +75      4,636
212  Total:            198,878,345    +370,787  4,238,503      +6,065

       NewDeaths TotalRecovered NewRecovered ActiveCases Serious,Critical  \
0    179,521,450       +271,005   15,118,392      90,326           25,514
1                    29,666,117                5,449,592           11,516
2           +393     30,846,509      +33,636     422,339            8,944
3                    18,619,542                  741,876            8,318
4           +789      5,625,890      +17,271     503,435            2,300
..           ...            ...          ...         ...              ...
208                          26                        2
209                          19                        1
210                           8                        1
211                      87,347          +24       1,022               25
212  179,521,450       +271,005   15,118,392      90,326         25,514.2

    Tot Cases/1M pop Deaths/1M pop
0              543.8
1            107,311         1,889
2             22,725           305
3             92,991         2,598
4             43,073         1,091
..               ...           ...
208            4,859
209            4,204           200
210               16             2
211               65             3
212            543.8

[213 rows x 12 columns]
"""
# If you don't need pandas index then you can try this :
df.reset_index(inplace=True)
# And to set # column index :
df.set_index("#",inplace=True)

# Now we got complete data, if we want we may save it in file as well 

pd.to_csv("<>.csv",index=False)
# or if excel 
pd.to_excel("<>.xlsx",index=False)

You are getting "5,547" lines of result cuz there are many empty rows and some unnecessary so it became that much big. And this reduced your manual work like that data dictionary, now you don't have to write column name one by one.

Related