Home > Enterprise >  How to scrape service and corresponding price, selected by string?
How to scrape service and corresponding price, selected by string?

Time:11-16

On specific, given website I want to:

  1. Check if specific service (provided by string) is available
  2. If available I want to get its price

Let's assume that pricing is stored in table which name I don't know.
I've used beautifulsoup to find this service by variable called strServiceVersion on webiste (looking for columns which contains string).

arrDataCell = soup.find_all('td', text=strServiceVersion)
            if len(arrDataCell)>0:      #found data cell with given string
                print("\nPrint whole data cell: ")
                print(arrDataCell)               
                print("\nPrint parent of the 1st data cell: ") #print below
                print(arrDataCell[0].parent())

So I know this text (name of service) exist on the webpage in some table.
I can print parent of the 1st data cell:

[<td > Some_Text_1</td>, Some_Text_2, <td >Price_1</td>, Price_2]

Now I know this X table has two columns and I know its class names (column-1 and column-2 I presume?)
Now I would like to get this table as an object and being able to loop through it and extract it by row and column.

How do I do that?
Or any other solution?

CodePudding user response:

Question could be improved by some details and expected result - However, to select a element by containing specific string you could go with:

for e in soup.select('td:-soup-contains("Kompleksowe badanie")'):
    print(e.text,e.find_next_sibling('td').text)

Note: soup.find_all('td', text=strServiceVersion) will only work if you have the exact matching string


Alternative would be to create a dict to filter:

{s.text:s.next_sibling.text for s in soup.select('td:has( td.column-2)')}

and too deal with | prices:

d = {}
for k in soup.select('td:has( td.column-2)'):
    v=k.next_sibling.text
    k=k.text
    if '|' in v:
        d.update(dict(zip([' '.join((k.split(':')[0], itm)) for itm in k.split(':')[-1].split(' | ')], v.split(' | '))))
    else:
        d.update({k:v})
d

Example

import requests
from bs4 import BeautifulSoup

headers = {'User-Agent':'Mozilla/5.0'}
url = 'https://optimdent.pl/cennik/'

soup = BeautifulSoup(requests.get(url, headers=headers).text)
   
d = {}
for k in soup.select('td:has( td.column-2)'):
    v=k.next_sibling.text
    k=k.text
    if '|' in v:
        d.update(dict(zip([' '.join((k.split(':')[0], itm)) for itm in k.split(':')[-1].split(' | ')], v.split(' | '))))
    else:
        d.update({k:v})
d

Output

A dict that could be filtered an manipulated:

{'Kompleksowe badanie stomatologiczne jamy ustnej': '150',
 'Kompleksowe badanie stomatologiczne jamy ustnej poszerzone o badanie stawów skroniowo-żuchwowych': '250',
 'Konsultacja: endodontyczna | periodontologiczna | chirurgiczna | protetyczna | implantologiczna': '150',
 'Konsultacja ortodontyczna': '250',
 'Całkowite opracowanie i odbudowa ubytku zęba materiałem Estelite Asteria  małe': '330',
 'Całkowite opracowanie i odbudowa ubytku zęba materiałem Estelite Asteria średnie': '380',
 'Całkowite opracowanie i odbudowa ubytku zęba materiałem Estelite Asteria duże': '420',
 'Całkowite opracowanie i wysokoestetyczna odbudowa ubytku zęba materiałem Enamel HRI  małe': '370',
 'Całkowite opracowanie i wysokoestetyczna odbudowa ubytku zęba materiałem Enamel HRI średnie': '410',
 'Całkowite opracowanie i wysokoestetyczna odbudowa ubytku zęba materiałem Enamel HRI duże': '450',
 'Leczenie kanałowe zęba  1 kanałowego': '800',
 'Leczenie kanałowe zęba 2 kanałowego': '1000',
 'Leczenie kanałowe zęba 3 kanałowego': '1300',
 'Leczenie kanałowe zęba 4 kanałowego': '1450',...}

CodePudding user response:

[I got soup simply with url='https://optimdent.pl/cennik/'; soup=BeautifulSoup(requests.get(url).content).]

If you're sure that the td cells have classes column-1 and column-2 respectively, you can get the first cell [if it contains strServiceVersion] of each row with such cells (and no other cells) with

tcrSel1 = 'tr:has(td.column-1~td.column-2):not(:has(td~td~td))' 
tcrSel2 = f'td.column-1:first-child:-soup-contains("{strServiceVersion}")' 
arrDataCell = soup.select(f'{tcrSel1} {tcrSel2}')

(NB: You'll probably need to have html5lib installed to be able use pseudo-classes like -soup-contains.)

It's pretty much the same as

arrDataCell = soup.find_all(
    lambda t: t.name == 'td' and 'column-1' in t.get('class', []) 
    and strServiceVersion in t.get_text() #and t.get_text()==strServiceVersion 
    and t.find_parent('tr') and t == t.find_parent('tr').td 
    and len(t.find_next_siblings('td', {'class': 'column-2'})) == 1
)

it's longer, but using find lambda lets you specify exact (full) text, and even use regex if you want.

(Btw, for this particular site, your soup.find_all('td', text=strServiceVersion) is probably also fine, but I couldn't know before how many tables/rows might contain the same text but have different structure.)



With either version,

dictList = [{
  'column-1': a1.get_text(strip=True), 'column-2': a2.get_text(strip=True) 
} for a1,a2 in [a.parent.find_all('td', recursive=False) for a in arrDataCell]]

should give you this list of dictionaries:

[
   {'column-1': 'Wybielanie zębów w gabinecie preparatem Prevdent', 'column-2': '1600'} ,
   {'column-1': 'Aparat stały (za jeden łuk): zamki metalowe | zamki ICONIX | zamki ceramiczne', 'column-2': '2500 | 2900 | 3500'} ,
   {'column-1': 'Wizyta kontrolna w trakcie noszenia aparatu: 1 łuk | 2 łuki', 'column-2': '240 | 280'} ,
   {'column-1': 'Leczenie aparatem INVISALIGN (przeźroczyste nakładki): diagnostyka | pakiet Standard | pakiet Full', 'column-2': '2000 | 11000 | 18000'} ,
]

OR, to get a single dictionary,

adcDict = {
    a1.get_text(' ', strip=True): a2.get_text(' ', strip=True) for 
    a1,a2 in [a.parent.find_all('td', recursive=False) for a in arrDataCell]
}
{
 'Wybielanie zębów w gabinecie preparatem Prevdent': '1600',
 'Aparat stały (za jeden łuk): zamki metalowe | zamki ICONIX | zamki ceramiczne': '2500 | 2900 | 3500',
 'Wizyta kontrolna w trakcie noszenia aparatu: 1 łuk | 2 łuki': '240 | 280',
 'Leczenie aparatem INVISALIGN (przeźroczyste nakładki): diagnostyka | pakiet Standard | pakiet Full': '2000 | 11000 | 18000'
}


Results will look a bit different with read_html, since it will grab the whole table instead of specific rows (and you can't specify the td class or cells per row).

# dfList = pandas.read_html(url, match=strServiceVersion) # OR
# dfList = pandas.read_html('\n'.join([str(a.find_parent('table')) for a in arrDataCell])) # OR
# dfList = pandas.read_html('<table>\n' '\n'.join([str(a.find_parent('tr')) '\n</table>' for a in arrDataCell])) # OR
dfList = pandas.read_html(str(soup), match=strServiceVersion)
dictList = []
for df in dfList: dictList  = df.to_dict('records')

(It looks like merged columns are split and duplicated...)

[
   {0: 'Licówka porcelanowa', 1: '2000'} ,
   {0: 'Dbamy o każdy szczegół wykonywanej pracy, aby przyszłe uzupełnienie w największym stopniu odzwierciedlało naturalny, piękny zdrowy uśmiech. Przed przystąpieniem do pracy wykonujemy waxup, dzięki któremu możemy zwizualizować i ocenić przyszłe efekty leczenia. Współpracujemy z uznanymi pracowniami dentystycznymi bazującymi na najnowszych materiałach oraz technologiach.', 1: 'Dbamy o każdy szczegół wykonywanej pracy, aby przyszłe uzupełnienie w największym stopniu odzwierciedlało naturalny, piękny zdrowy uśmiech. Przed przystąpieniem do pracy wykonujemy waxup, dzięki któremu możemy zwizualizować i ocenić przyszłe efekty leczenia. Współpracujemy z uznanymi pracowniami dentystycznymi bazującymi na najnowszych materiałach oraz technologiach.'} ,
   {0: 'Bonding zębów metodą Flow-Injection (za ząb)', 1: '450'} ,
   {0: 'Dzięki bogatemu doświadczeniu i wiedzy naszych specjalistów, jesteśmy w stanie przywrócić naturalny i piękny uśmiech, którym możesz dzielić się z innymi. Często przed wykonaniem odbudowy wykonujemy wizualizację w jamie ustnej, dzięki czemu możesz ocenić efekt przed ostateczną realizacją. Bonding wykonujemy techniką Flow-Injection, która jest zdecydowanie dokładniejsza niż tzw. odbudowa z ręki.', 1: 'Dzięki bogatemu doświadczeniu i wiedzy naszych specjalistów, jesteśmy w stanie przywrócić naturalny i piękny uśmiech, którym możesz dzielić się z innymi. Często przed wykonaniem odbudowy wykonujemy wizualizację w jamie ustnej, dzięki czemu możesz ocenić efekt przed ostateczną realizacją. Bonding wykonujemy techniką Flow-Injection, która jest zdecydowanie dokładniejsza niż tzw. odbudowa z ręki.'} ,
   {0: 'Wybielanie zębów w gabinecie preparatem Prevdent', 1: '1600'} ,
   {0: 'Metoda wybielania preparatem Prevdent jest w 100% bezpieczna i pozwala uzyskać naturalny efekt estetyczny. Cała procedura wykonywana jest w gabinecie stomatologicznym w trakcie jednej wizyty, która trwa około 2 godzin.', 1: 'Metoda wybielania preparatem Prevdent jest w 100% bezpieczna i pozwala uzyskać naturalny efekt estetyczny. Cała procedura wykonywana jest w gabinecie stomatologicznym w trakcie jednej wizyty, która trwa około 2 godzin.'} ,
   {0: 'Wybielanie nakładkowe (szyny   4 strzykawki Opalescence)', 1: '950'} ,
   {0: 'Metoda nakładkowa polega na trójwymiarowym zeskanowaniu zębów pacjenta, a następnie przygotowaniu specjalnych, indywidualnych szyn, do których nakłada się żel wybielający Opalescence. Procedura wymaga 2 wizyt w odstępie paru dni (skan wewnątrzustny oraz oddanie nakładek). Efekt wybielania jest po około 7-10 dniach noszenia nakładek.', 1: 'Metoda nakładkowa polega na trójwymiarowym zeskanowaniu zębów pacjenta, a następnie przygotowaniu specjalnych, indywidualnych szyn, do których nakłada się żel wybielający Opalescence. Procedura wymaga 2 wizyt w odstępie paru dni (skan wewnątrzustny oraz oddanie nakładek). Efekt wybielania jest po około 7-10 dniach noszenia nakładek.'} ,
   {0: 'Zabiegi z zakresu ortodoncji wykonuje lek. dent. Karolina Różycka-Popadiak (właściciel OptimDent). W trakcie leczenia wykorzystujemy światowe standardy i rozwiązania. Każdy przypadek jest inny dlatego leczenie rozpoczynamy od wnikliwej analizy klinicznej, starając się dobrać jak najodpowiedniejszy sposób leczenia.', 1: 'Zabiegi z zakresu ortodoncji wykonuje lek. dent. Karolina Różycka-Popadiak (właściciel OptimDent). W trakcie leczenia wykorzystujemy światowe standardy i rozwiązania. Każdy przypadek jest inny dlatego leczenie rozpoczynamy od wnikliwej analizy klinicznej, starając się dobrać jak najodpowiedniejszy sposób leczenia.'} ,
   {0: 'Aparat stały (za jeden łuk): zamki metalowe | zamki ICONIX | zamki ceramiczne', 1: '2500 | 2900 | 3500'} ,
   {0: 'Wizyta kontrolna w trakcie noszenia aparatu: 1 łuk | 2 łuki', 1: '240 | 280'} ,
   {0: 'Leczenie aparatem INVISALIGN (przeźroczyste nakładki): diagnostyka | pakiet Standard | pakiet Full', 1: '2000 | 11000 | 18000'} ,
   {0: 'Posiadamy najnowocześniejszy aparat rentgenowski. Aby wykonać zdjęcie nie musisz się umawiać, cała procedura trwa kilka minut, a wynik jest dostępny tuż po wykonaniu zdjęcia w wersji cyfrowej lub na kliszy.', 1: 'Posiadamy najnowocześniejszy aparat rentgenowski. Aby wykonać zdjęcie nie musisz się umawiać, cała procedura trwa kilka minut, a wynik jest dostępny tuż po wykonaniu zdjęcia w wersji cyfrowej lub na kliszy.'} ,
   {0: 'Pantomogram (zdjęcie 2D wszystkich zębów)', 1: '90'} ,
   {0: 'Cefalometria (zdjęcie boczne 2D używane głównie w ortodoncji)', 1: '90'} ,
   {0: 'Tomografia komputerowa: odcinka | szczęki lub żuchwy | całość', 1: '140 | 250 | 350'} ,
   {0: 'Pakiet Pantomogram   Cefalometria', 1: '150'} ,
]

Although, you could filter down to rows with numbers in the second column with something like

dictList = [d for d in dictList if d[1].split(' | ')[0].isdigit()]
[
   {0: 'Licówka porcelanowa', 1: '2000'} ,
   {0: 'Bonding zębów metodą Flow-Injection (za ząb)', 1: '450'} ,
   {0: 'Wybielanie zębów w gabinecie preparatem Prevdent', 1: '1600'} ,
   {0: 'Wybielanie nakładkowe (szyny   4 strzykawki Opalescence)', 1: '950'} ,
   {0: 'Aparat stały (za jeden łuk): zamki metalowe | zamki ICONIX | zamki ceramiczne', 1: '2500 | 2900 | 3500'} ,
   {0: 'Wizyta kontrolna w trakcie noszenia aparatu: 1 łuk | 2 łuki', 1: '240 | 280'} ,
   {0: 'Leczenie aparatem INVISALIGN (przeźroczyste nakładki): diagnostyka | pakiet Standard | pakiet Full', 1: '2000 | 11000 | 18000'} ,
   {0: 'Pantomogram (zdjęcie 2D wszystkich zębów)', 1: '90'} ,
   {0: 'Cefalometria (zdjęcie boczne 2D używane głównie w ortodoncji)', 1: '90'} ,
   {0: 'Tomografia komputerowa: odcinka | szczęki lub żuchwy | całość', 1: '140 | 250 | 350'} ,
   {0: 'Pakiet Pantomogram   Cefalometria', 1: '150'} ,
]
  • Related