Моя проблема в том, что я не могу использовать bs4 для очистки субрейтингов в его обзорах. Ниже пример:
До сих пор я обнаружил, где находятся эти звезды, но их коды одинаковы независимо от цвета (т. Е. Зеленый или серый) ... Мне нужно определить цвет, чтобы определить рейтинги, а не просто соскребать звезды. . Ниже мой код:
url='https://www.glassdoor.com/Reviews/Walmart-Reviews-E715_P2.htm?filter.iso3Language=eng'
response = requests.get(url)
soup = BeautifulSoup(response.text, 'html.parser')
com = soup.find(class_ = "ratingNumber mr-xsm")
com1 = soup.find(class_ = "gdReview")
com1_1 = com1.find(class_ = "content")
@Jaevapple- теперь я понимаю лучше (вы должны указать в своем вопросе, что вам также нужны подрейтинги, поскольку сразу видимые рейтинги также имеют числовое значение, поэтому цвета звездочек не важны.)






Для получения разбивки звездного рейтинга (который, кажется, не имеет числового отображения или мета-значения), я не думаю, что есть какой-то очень простой и прямой короткий метод, поскольку он выполняется с помощью css в теге стиля, связанном с класс элемента-контейнера.
Вы можете использовать что-то вроде soup.select('style:-soup-contains(".css-1nuumx7")') [часть css-1nuumx7 специфична для упомянутого выше рейтинга], но :-soup-contains требует синтаксического анализатора html5lib и может быть немного медленным, поэтому лучше выяснить атрибут data-emotion-css тег style вместо этого:
def getDECstars(starCont, mSoup, outOf=5, isv=False):
classList = starCont.get('class', [])
if type(classList) != list: classList = [classList]
classList = [str(c) for c in classList if str(c).startswith('css-')]
if not classList:
if isv: print('Stars container has no "css-" class')
return None
demc = classList[0].replace('css-', '', 1)
demc_sel = f'style[data-emotion-css = "{demc}"]'
cssStyle = mSoup.select_one(demc_sel)
if not cssStyle:
if isv: print(f'Nothing found with selector {demc_sel}')
return None
cssStyle = cssStyle.get_text()
errMsg = ''
if '90deg,#0caa41 ' not in cssStyle: errMsg += 'No #0caa41'
if '%' not in cssStyle.split('90deg,#0caa41 ', 1)[-1][:20]:
errMsg += ' No %'
if not errMsg:
rPerc = cssStyle.split('90deg,#0caa41 ', 1)[-1]
rPerc = rPerc.split('%')[0]
try:
rPerc = float(rPerc)
if 0 <= rPerc <= 100:
if type(outOf) == int and outOf > 0: rPerc = (rPerc/100)*outOf
return float(f'{float(rPerc):.3}')
errMsg = f'{demc_sel} --> "{rPerc}" is out of range'
except: errMsg = f'{demc_sel} --> cannot convert to float "{rPerc}"'
if isv: print(f'{demc_sel} --> unexpected format {errMsg}')
return None
ИЛИ, если вам все равно, почему отсутствует рейтинг:
def getDECstars(starCont, mSoup, outOf=5, isv=False):
try:
demc = [c for c in starCont.get('class', []) if c[:4]=='css-'][0].replace('css-', '', 1)
demc_sel = f'style[data-emotion-css = "{demc}"]'
rPerc = float(mSoup.select_one(demc_sel).get_text().split('90deg,#0caa41 ', 1)[1].split('%')[0])
return float(f'{(rPerc/100)*outOf if type(outOf) == int and outOf > 0 else rPerc:.3}')
except: return None
Вот пример того, как вы можете его использовать:
pcCon = 'div.px-std:has(h2 > a.reviewLink) + div.px-std'
pcDiv = f'{pcCon} div.v2__EIReviewDetailsV2__fullWidth'
refDict = {
'rating_num': 'span.ratingNumber',
'emp_status': 'div:has(> div > span.ratingNumber) + span',
'header': 'h2 > a.reviewLink',
'subheader': 'h2:has(> a.reviewLink) + span',
'pros': f'{pcDiv}:first-of-type > p.pb',
'cons': f'{pcDiv}:nth-of-type(2) > p.pb'
}
subRatSel = 'div:has(> .ratingNumber) ~ aside ul > li:has(div ~ div)'
empRevs = []
for r in soup.select('li[id^ = "empReview_"]'):
rDet = {'reviewId': r.get('id')}
for sr in r.select(subRatSel):
k = sr.select_one('div:first-of-type').get_text(' ').strip()
sval = getDECstars(sr.select_one('div:nth-of-type(2)'), soup)
rDet[f'[rating] {k}'] = sval
for k, sel in refDict.items():
sval = r.select_one(sel)
if sval: sval = sval.get_text(' ').strip()
rDet[k] = sval
empRevs.append(rDet)
Если empRevs рассматривается как таблица:
Markdown for the table above was printed with pandas:
erdf = pandas.DataFrame(empRevs).set_index('reviewId') erdf['pros'] = [p[:30] + '...' if len(p) > 33 else p for p in erdf['pros']] erdf['cons'] = [p[:30] + '...' if len(p) > 33 else p for p in erdf['cons']] print(erdf.to_markdown())
Не мог бы поблагодарить вас еще больше! Это АБСОЛЮТНО тот ответ, который я ищу. Я выучу ваш код наизусть. Это выглядит очень круто для меня, и я буду гуглить, чтобы узнать это. Большое спасибо.
@Jaevapple Я был бы рад помочь, чем смогу, но боюсь, что не вижу никаких изменений в вашем вопросе, и я не совсем понимаю, что вы подразумеваете под «каждый отзыв имеет одинаковое общее количество обзоры"... вы можете найти количество страниц, разделив текст из soup.select_one('div[data-test = "pagination-footer-text"]') [ внизу страницы ], а ссылку на следующую страницу из href из soup.select_one('li:has(a.page.selected) + li a.page[href]') [ см. это для примера того, как очистить результаты с разбивкой на страницы ]
@Jaevapple Я видел в вашем вопросе немного об отсутствующих подрейтингах из 4 частей. Я добавил обновленную версию getDECstars в https://pastebin.com/Q0GLwRv9 (новые/измененные строки отмечены ## edit), и это должно решить эту конкретную проблему.
@Jaevapple, но проблема остается из-за сбоев requests ... вы можете легко использовать селен, заменив свою функцию extract(pg) на что-то вроде этого, но похоже, что некоторые из необходимых тегов style исчезают после загрузки страницы, поэтому ни один из подрейтинги могут показывать
comдает вам рейтинг в виде числа - зачем вам после этого цвета звезд?