【问题标题】:How to scrape embedded links and tabular information如何抓取嵌入的链接和表格信息
【发布时间】:2019-06-01 10:49:31
【问题描述】:

我正在尝试收集有关 this website 上可用数据集的信息。

我想收集资源的 URL 以及至少数据集的标题。

this resource为例,我想捕获嵌入在“Go to resource”中的URL和表格中列出的标题:

我已经创建了一个基本的刮刀,但它似乎不起作用:

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)

我想将输出写入包含 URL 和标题列的 CSV。

这是所需输出的示例

非常感谢任何帮助

【问题讨论】:

  • 你是如何通过第一个网址获得第二个网址的?

标签: python web-scraping beautifulsoup python-requests


【解决方案1】:

查看API for the datasets,这可能是最简单的方法。

与此同时,您可以通过以下方式从这些页面获取 id 级别的 API 链接,并将所有包的整个包信息存储在一个列表中,data_sets,以及另一个变量(@ 987654334@)。如果有更好的方法,请务必查看 API 文档 - 例如,如果 id 可以分批提交而不是按 id 提交,那就太好了。

下面的答案是利用文档中详述的端点,该端点用于获取数据集、资源或其他对象的完整 JSON 表示

获取当前登陆页面上的第一个结果:

Guyra 植被 1:25000 地图 VIS_ID 240

我们想要父级h3 的最后一个子级a,父级具有.dataset-item。在下面,选择器之间的空格是descendant combinators

.dataset-item h3 a:last-child

您可以将其缩短为 h3 a:last-child,以提高效率。

这种关系可靠地选择页面上的所有相关链接。

继续这个示例,访问检索到的第一个列出项目的 url,我们可以使用 api 端点(检索与此包相关的 json)通过属性=值选择器找到 id,其中包含、*、运算符。我们知道这个特定的 api 端点有一个公共字符串,所以我们在 href 属性值上匹配子字符串:

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

域可能会有所不同,并且某些检索到的链接是相对的,因此我们必须测试是否相对并添加适当的域。

该匹配的首页 html:


注意事项:

  1. data_sets 是一个包含每个包的所有包数据的列表,并且内容广泛。我这样做是为了让您有兴趣查看这些包中的内容(除了查看 API 文档)
  2. 您可以通过页面上的汤对象获取总页数

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

您可以更改循环以减少页面。

  1. 会话对象用于efficiency of re-using connection。我敢肯定还有其他改进。特别是我会寻找任何减少请求数量的方法(例如为什么我提到寻找批处理 id 端点)。
  2. 在返回的包中可以有多个资源 url。参见示例here。您可以编辑代码来处理这个问题。

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)

所有页面

(运行时间很长,所以考虑线程/异步):

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)

【讨论】:

  • 非常感谢您的详细解释和代码。这是非常有见地和有用的。
  • 还有一些改进的余地,但请试一试,让我知道它对您的作用。
  • 它工作得很好,正是我需要的:) 添加一个额外的列来获取数据门户信息是否足够简单?在您提供的示例中,有一个用于 '.data-portal="SEED"' 的类我可以将它作为额外变量添加到 get_links 函数中,还是需要它自己的独立函数?我想为该类包含文本字符串的每个资源输出数据门户信息。
  • 您能否提供一个示例 url 并说明应该检索什么?
  • 当然。所以这个url 应该返回“SEED”,这个url 应该返回“ePlanning”。我有highlighted 我想捕获的元素,特别是'.data-portal=' 字段。基本上,我要获取的是“来自 [portal]”条目,您可以在目录中看到 here(如果适用)。
【解决方案2】:

为简单起见,使用 selenium 包:

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

【讨论】:

    猜你喜欢
    • 2014-05-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-02-28
    • 2021-11-01
    • 2017-04-28
    • 2021-01-02
    相关资源
    最近更新 更多