Как очистить встроенные ссылки и табличную информацию - PullRequest
1 голос
/ 01 июня 2019

Я пытаюсь собрать информацию о наборах данных, доступных на этом сайте .

Я хочу собрать URL-адреса ресурсов и, по крайней мере, заголовок набора данных.

Используя этот ресурс в качестве примера, я хочу захватить URL-адрес, встроенный в "Перейти к ресурсу" и заголовок, указанный в таблице: enter image description here

Я создал простой скребок, но он не работает:

import requests
import csv
from bs4 import BeautifulSoup

site = requests.get('https://data.nsw.gov.au/data/dataset');
data_list=[]

if site.status_code is 200:
    content = BeautifulSoup(site.content, 'html.parser')
    internals = content.select('.resource-url-analytics')
    for url in internals:
        title = internals.select=('.resource-url-analytics')[0].get_text()
        link = internals.select=('.resource-url-analytics')[0].get('href')
        new_data = {"title": title, "link": link}
        data_list.append(new_data)
    with open ('selector.csv','w') as file:
            writer = csv.DictWriter(file, fieldnames = ["dataset", "link"], delimiter = ';')
            writer.writeheader()
            for row in data_list:
                writer.writerow(row)

Я хотел бы записать вывод в CSV со столбцами для URL-адресов и заголовков.

Это пример желаемого вывода

Desired output table

Большое спасибо за любую помощь

Ответы [ 2 ]

1 голос
/ 01 июня 2019

Посмотрите на 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 для небольшого увеличения эффективности.

Это отношение надежно выбирает все релевантные ссылки на странице.

enter image description here

Продолжая этот пример, посещая этот извлеченный URL для первого элемента в списке, мы можем найти идентификатор, используя конечную точку api (которая получает json, связанную с этим пакетом), через селектор атрибута = значение с оператором contains, *,. Мы знаем, что эта конкретная конечная точка API имеет общую строку, поэтому подстрока соответствует значению атрибута href:

[href*="/api/3/action/package_show?id="]

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

HTML-код первой страницы для этого совпадения:

enter image description here


Примечания:

  1. data_sets представляет собой список, содержащий все данные пакета для каждого пакета, и является обширным. Я сделал это на тот случай, если вам интересно посмотреть, что находится в этих пакетах (помимо просмотра документации по API)
  2. Вы можете получить общее количество страниц объекта супа на странице с помощью

   num_pages = int(soup.select('[href^="/data/dataset?page="]')[-2].text)

Вы можете изменить цикл для меньшего количества страниц.

  1. Объект сеанса используется для эффективности повторного использования соединения . Я уверен, что есть и другие улучшения. В частности, я бы искал любой метод, который уменьшил бы количество запросов (почему я упомянул, например, поиск конечной точки идентификатора пакета).
  2. В возвращаемом пакете может быть только один 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)
0 голосов
/ 01 июня 2019

Для простоты используйте селеновый пакет:

from selenium import webdriver
import os

# initialise browser
browser = webdriver.Chrome(os.getcwd() + '/chromedriver')
browser.get('https://data.nsw.gov.au/data/dataset')

# find all elements by xpath
get_elements = browser.find_elements_by_xpath('//*[@id="content"]/div/div/section/div/ul/li/div/h3/a[2]')

# collect data
data = []
for item in get_elements:
    data.append((item.text, item.get_attribute('href')))

Выход:

('Vegetation of the Guyra 1:25000 map sheet VIS_ID 240', 'https://datasets.seed.nsw.gov.au/dataset/vegetation-of-the-guyra-1-25000-map-sheet-vis_id-2401ee52')
('State Vegetation Type Map: Riverina Region Version v1.2 - VIS_ID 4469', 'https://datasets.seed.nsw.gov.au/dataset/riverina-regional-native-vegetation-map-version-v1-0-vis_id-4449')
('Temperate Highland Peat Swamps on Sandstone (THPSS) spatial distribution maps...', 'https://datasets.seed.nsw.gov.au/dataset/temperate-highland-peat-swamps-on-sandstone-thpss-vegetation-maps-vis-ids-4480-to-4485')
('Environmental Planning Instrument - Flood', 'https://www.planningportal.nsw.gov.au/opendata/dataset/epi-flood')

and so on
...