Посмотрите на API для наборов данных , который, вероятно, будет самым простым способом сделать это.
А пока вот как вы можете получить ссылки API на этих страницах на уровне идентификатора и сохранить всю информацию о пакете для всех пакетов в одном списке, data_sets
, и только интересующую информацию в другой переменной (results
). Обязательно ознакомьтесь с документацией по API, если есть лучший метод - например, было бы неплохо, если бы идентификаторы можно было отправлять партиями, а не по идентификатору.
Ответ ниже использует конечную точку, подробно описанную в документации, которая используется для получения полного представления JSON набора данных, ресурса или другого объекта
Получение текущего первого результата на целевой странице:
Карта растительности Гайры 1: 25000 VIS_ID 240 .
Нам нужен последний ребенок a
родителя h3
, у которого родитель имеет класс .dataset-item
. Ниже промежутки между селекторами являются комбинаторами-потомками .
.dataset-item h3 a:last-child
Вы можете сократить это значение до h3 a:last-child
для небольшого увеличения эффективности.
Это отношение надежно выбирает все релевантные ссылки на странице.
Продолжая этот пример, посещая этот извлеченный URL для первого элемента в списке, мы можем найти идентификатор, используя конечную точку api (которая получает json, связанную с этим пакетом), через селектор атрибута = значение с оператором contains, *,. Мы знаем, что эта конкретная конечная точка API имеет общую строку, поэтому подстрока соответствует значению атрибута href
:
[href*="/api/3/action/package_show?id="]
Домен может отличаться, и некоторые найденные ссылки являются относительными, поэтому мы должны проверить, являются ли они относительными, и добавить соответствующий домен.
HTML-код первой страницы для этого совпадения:
Примечания:
data_sets
представляет собой список, содержащий все данные пакета для каждого пакета, и является обширным. Я сделал это на тот случай, если вам интересно посмотреть, что находится в этих пакетах (помимо просмотра документации по API)
- Вы можете получить общее количество страниц объекта супа на странице с помощью
num_pages = int(soup.select('[href^="/data/dataset?page="]')[-2].text)
Вы можете изменить цикл для меньшего количества страниц.
- Объект сеанса используется для эффективности повторного использования соединения . Я уверен, что есть и другие улучшения. В частности, я бы искал любой метод, который уменьшил бы количество запросов (почему я упомянул, например, поиск конечной точки идентификатора пакета).
- В возвращаемом пакете может быть только один URL-адрес ресурса. Смотрите пример здесь . Вы можете редактировать код, чтобы справиться с этим.
Python:
from bs4 import BeautifulSoup as bs
import requests
import csv
from urllib.parse import urlparse
json_api_links = []
data_sets = []
def get_links(s, url, css_selector):
r = s.get(url)
soup = bs(r.content, 'lxml')
base = '{uri.scheme}://{uri.netloc}'.format(uri=urlparse(url))
links = [base + item['href'] if item['href'][0] == '/' else item['href'] for item in soup.select(css_selector)]
return links
results = []
#debug = []
with requests.Session() as s:
for page in range(1,2): #you decide how many pages to loop
links = get_links(s, 'https://data.nsw.gov.au/data/dataset?page={}'.format(page), '.dataset-item h3 a:last-child')
for link in links:
data = get_links(s, link, '[href*="/api/3/action/package_show?id="]')
json_api_links.append(data)
#debug.append((link, data))
resources = list(set([item.replace('opendata','') for sublist in json_api_links for item in sublist])) #can just leave as set
for link in resources:
try:
r = s.get(link).json() #entire package info
data_sets.append(r)
title = r['result']['title'] #certain items
if 'resources' in r['result']:
urls = ' , '.join([item['url'] for item in r['result']['resources']])
else:
urls = 'N/A'
except:
title = 'N/A'
urls = 'N/A'
results.append((title, urls))
with open('data.csv','w', newline='') as f:
w = csv.writer(f)
w.writerow(['Title','Resource Url'])
for row in results:
w.writerow(row)
Все страницы
(очень долго работает, поэтому рассмотрим многопоточность / asyncio):
from bs4 import BeautifulSoup as bs
import requests
import csv
from urllib.parse import urlparse
json_api_links = []
data_sets = []
def get_links(s, url, css_selector):
r = s.get(url)
soup = bs(r.content, 'lxml')
base = '{uri.scheme}://{uri.netloc}'.format(uri=urlparse(url))
links = [base + item['href'] if item['href'][0] == '/' else item['href'] for item in soup.select(css_selector)]
return links
results = []
#debug = []
with requests.Session() as s:
r = s.get('https://data.nsw.gov.au/data/dataset')
soup = bs(r.content, 'lxml')
num_pages = int(soup.select('[href^="/data/dataset?page="]')[-2].text)
links = [item['href'] for item in soup.select('.dataset-item h3 a:last-child')]
for link in links:
data = get_links(s, link, '[href*="/api/3/action/package_show?id="]')
json_api_links.append(data)
#debug.append((link, data))
if num_pages > 1:
for page in range(1, num_pages + 1): #you decide how many pages to loop
links = get_links(s, 'https://data.nsw.gov.au/data/dataset?page={}'.format(page), '.dataset-item h3 a:last-child')
for link in links:
data = get_links(s, link, '[href*="/api/3/action/package_show?id="]')
json_api_links.append(data)
#debug.append((link, data))
resources = list(set([item.replace('opendata','') for sublist in json_api_links for item in sublist])) #can just leave as set
for link in resources:
try:
r = s.get(link).json() #entire package info
data_sets.append(r)
title = r['result']['title'] #certain items
if 'resources' in r['result']:
urls = ' , '.join([item['url'] for item in r['result']['resources']])
else:
urls = 'N/A'
except:
title = 'N/A'
urls = 'N/A'
results.append((title, urls))
with open('data.csv','w', newline='') as f:
w = csv.writer(f)
w.writerow(['Title','Resource Url'])
for row in results:
w.writerow(row)