Правильная комбинация div-класса для soup.select()

#python #html #web-scraping #beautifulsoup

#python #HTML #очистка веб-страниц #beautifulsoup

Вопрос:

Я разрабатываю некоторый код очистки, и он продолжает возвращать некоторые ошибки, с которыми, как я полагаю, другие могли бы помочь.

Сначала я запускаю этот фрагмент:

 import  pandas as pd
from urllib.parse import urljoin
import requests 

base = "http://www.reed.co.uk/jobs"

url = "http://www.reed.co.uk/jobs?datecreatedoffset=Todayamp;pagesize=100"
r = requests.get(url).content
soup = BShtml(r, "html.parser")

df = pd.DataFrame(columns=["links"], data=[urljoin(base, a["href"]) for a in soup.select("div.pages a.page")])
df
 

Я запускаю приведенный выше фрагмент на первой странице сегодняшних объявлений о вакансиях. И я извлекаю URL-адреса внизу страницы, чтобы найти общее количество страниц, существующих на данный момент времени. Приведенные ниже регулярные выражения устраняют это для меня:

 df['partone'] = df['links'].str.extract('([a-z][a-z][a-z][a-z][a-z][a-z]=[0-9][0-9].)', expand=True)
df['maxlink'] = df['partone'].str.extract('([0-9][0-9][0-9])', expand=True)
pagenum = df['maxlink'][4]
pagenum = pd.to_numeric(pagenum, errors='ignore')
 

Обратите внимание на третью строку выше, количество страниц всегда содержится во втором из последних (из пяти) URL-адресов в этом списке. Я уверен, что есть более элегантный способ сделать это, но его достаточно как есть. Затем я передаю число, которое я взял из URL, в цикл:

 result_set = []

loopbasepref = 'http://www.reed.co.uk/jobs?cached=Trueamp;pageno='
loopbasesuf = 'amp;datecreatedoffset=Todayamp;pagesize=100'
for pnum in range(1,pagenum):
    url = loopbasepref   str(pnum)   loopbasesuf
    r = requests.get(url).content
    soup = BShtml(r, "html.parser")
    df2 = pd.DataFrame(columns=["links"], data=[urljoin(base, a["href"]) for a in  soup.select("div", class_="results col-xs-12 col-md-10")])
    result_set.append(df2)
    print(df2)
 

Здесь я получаю сообщение об ошибке. То, что я пытаюсь сделать, это перебрать все страницы, на которых перечислены задания, начиная со страницы 1 и переходя на страницу N, где N = число страниц, а затем извлечь URL-адрес, который ссылается на каждую отдельную страницу задания, и сохранить его во фрейме данных. Я пробовал различные комбинации soup.select("div", class_="") , но каждый раз получаю сообщение об ошибке следующего содержания: TypeError: select() got an unexpected keyword argument 'class_' .

Если у кого-нибудь есть какие-либо мысли по этому поводу и он может видеть хороший путь вперед, я был бы признателен за помощь!

Приветствия

Крис

Комментарии:

1. откуда берется BShtml?

2. Извините за это! Я тоже импортировал beautifulsoup4, но случайно не включил строку, в которой я это делаю. Прежде import pandas as pd чем я написал from bs4 import BeautifulSoup as BShtml

3. Почему вы создаете несколько фреймов данных?

Ответ №1:

Вы можете просто продолжать цикл, пока не появится следующая страница:

 import  requests
from bs4 import BeautifulSoup
from urllib.parse import  urljoin

base = "http://www.reed.co.uk"
url = "http://www.reed.co.uk/jobs?datecreatedoffset=Todayamp;pagesize=100"

def all_urls():
    r = requests.get(url).content
    soup = BeautifulSoup(r, "html.parser")
    # get the urls from the first page
    yield  [urljoin(base, a["href"]) for a in soup.select("div.details h3.title a[href^=/jobs]")]
    nxt = soup.find("a", title="Go to next page")
    # title="Go to next page" is missing when there are no more pages
    while nxt:
        # wash/repeat until no more pages
        r = requests.get(urljoin(base, nxt["href"])).content
        soup = BeautifulSoup(r, "html.parser")
        yield  [urljoin(base, a["href"]) for a in soup.select("div.details h3.title a[href^=/jobs]")]
        nxt = soup.find("a", title="Go to next page")
 

Просто выполните цикл над функцией генератора, чтобы получить URL-адреса с каждой страницы:

 for u in (all_urls()):
    print(u)
 

Я также использую a[href^=/jobs] в селекторе, поскольку есть и другие совпадающие теги, поэтому мы просто извлекаем пути задания.

В вашем собственном коде правильный способ использования селектора будет:

 soup.select("div.results.col-xs-12.col-md-10")
 

Ваш синтаксис предназначен для find или find_all, где вы используете class_=... для классов css:

 soup.find_all("div", class_="results col-xs-12 col-md-10")
 

Но это все равно неправильный селектор.

Не уверен, почему вы создаете несколько dfs, но если это то, что вы хотите:

 def all_urls():
    r = requests.get(url).content
    soup = BeautifulSoup(r, "html.parser")
    yield pd.DataFrame([urljoin(base, a["href"]) for a in soup.select("div.details h3.title a[href^=/jobs]")],
                       columns=["Links"])
    nxt = soup.find("a", title="Go to next page")
    while nxt:
        r = requests.get(urljoin(base, nxt["href"])).content
        soup = BeautifulSoup(r, "html.parser")
        yield pd.DataFrame([urljoin(base, a["href"]) for a in soup.select("div.details h3.title a[href^=/jobs]")],
                           columns=["Links"])
        nxt = soup.find("a", title="Go to next page")


dfs = list(all_urls())
 

Это даст вам список dfs:

 In [4]: dfs = list(all_urls())
dfs[0].head()
In [5]: dfs[0].head(10)
Out[5]: 
                                               Links
0  http://www.reed.co.uk/jobs/tufting-manager/308...
1  http://www.reed.co.uk/jobs/financial-services-...
2  http://www.reed.co.uk/jobs/head-of-finance-mul...
3  http://www.reed.co.uk/jobs/class-1-drivers-req...
4  http://www.reed.co.uk/jobs/freelance-middlewei...
5  http://www.reed.co.uk/jobs/sage-200-consultant...
6  http://www.reed.co.uk/jobs/bereavement-support...
7  http://www.reed.co.uk/jobs/property-letting-ma...
8  http://www.reed.co.uk/jobs/graduate-recruitmen...
9  http://www.reed.co.uk/jobs/solutions-delivery-...
 

Но если вам нужен только один, тогда используйте исходный код с помощью itertools.chain:

  from itertools import chain
 df = pd.DataFrame(columns=["links"], data=list(chain.from_iterable(all_urls())))
 

Что даст вам все ссылки в одном df:

 In [7]:  from itertools import chain
   ...:  df = pd.DataFrame(columns=["links"], data=list(chain.from_iterable(all_
   ...: urls())))
   ...: 

In [8]: df.size
Out[8]: 675