如何从下拉列表中抓取选项并将其存储在表中?

问题描述 投票:2回答:2

我正在尝试制作一个带有分析的交互式仪表板,基于汽车方面。我希望用户能够选择汽车品牌,例如宝马,奥迪等,并且基于这个选择,他将只有avaiablity选择宝马/奥迪等车型。选择每个品牌后我遇到了问题,我无法废弃属于该品牌的型号。我正在抓取的页面: 主页 - > https://www.otomoto.pl/osobowe/子车品牌页面示例 - > https://www.otomoto.pl/osobowe/audi/

我试图废弃每个选项,所以稍后我可以以某种方式清理数据以仅存储模型

码:

otomoto_models - paste0("https://www.otomoto.pl/osobowe/"audi/")
models <- read_html(otomoto_models) %>%
   html_nodes("option") %>%
   html_text()

但它只是通过页面引擎类型等可用的其他选项来抓取品牌。在检查元素后,我可以清楚地看到模型类型。

otomoto <- "https://www.otomoto.pl/osobowe/"


brands <- read_html(otomoto) %>%
  html_nodes("option") %>%
  html_text() 

brands <- data.frame(brands)

for (i in 1:nrow(brands)){
  no_marka_pojazdu <- i
    if(brands[i,1] == "Marka pojazdu"){
      break
    }
}
no_marka_pojazdu <- no_marka_pojazdu + 1 
for (i in 1:nrow(brands)){
  zuk <- i
  if(substr(brands[i,1],1,3) == "Żuk"){
    break
  }
}

Modele_pojazdow <- as.character(brands[no_marka_pojazdu:zuk,1])
Modele_pojazdow <- removeNumbers(Modele_pojazdow)
Modele_pojazdow <- substr(Modele_pojazdow,1,nchar(Modele_pojazdow)-2)
Modele_pojazdow <- data.frame(Modele_pojazdow)

以上代码仅用于在网页上挑选支持的汽车品牌并将其存储在数据框中。有了这个,我就可以创建html链接并将所有内容指向一个选定的品牌。

我希望有类似的对象“Modele_pojazdow”,但模型限制在以前选择的汽车品牌。

带有模型的下拉列表显示为白色框,右侧的“Audi”框旁边有“Model pojazdu”文本。

r web-scraping rvest
2个回答
3
投票

希望人们可以专注于高级流程而不是实际解决方案的细节。

有些人可能对解决方案语言是Python不屑一顾,但这样做的目的是为你提供一些指导。我很久没写R了,所以Python更快。如果我有时间,并且意志力,EDIT:R脚本现在已添加

概要:

可以使用value的css选择器从返回的每个节点的#param571 option属性中获取第一个下拉选项。这使用id selector (#)来定位父下拉列表select元素,然后使用option中的type descendant combination选择器来指定其中的option标记元素。可以通过xhr请求检索应用此选择器组合的html到最初提供的url。您希望返回的nodeList迭代;类似于使用js document.querySelectorAll应用选择器。

该页面使用ajax POST请求根据您的第一个下拉选项更新第二个下拉列表。您的第一个下拉选项确定参数search[filter_enum_make]的值,该参数在对服务器的POST请求中使用。随后的响应包含可用选项的列表(它包括一些可以修剪的案例备选方案)。

我使用fiddler捕获了POST请求。这向我展示了请求正文中的请求标头和参数。屏幕截图示例显示在最后。

从响应文本IMO中提取选项的最简单方法是将正确的字符串用于正则表达式(我通常不建议使用正则表达式来处理html,但在这种情况下它很适合我们)。如果您不想使用正则表达式,则可以从id为data-facets的元素的body-container属性中获取相关信息。对于非正则表达式版本,您需要处理不带引号的nulls,并检索其键为filter_enum_model的内部字典。我在最后展示了一个函数重写来处理这个问题。

检索到的字符串是字典的字符串表示。这需要转换为实际的字典对象,然后您可以从中提取选项值。编辑:由于R没有字典对象,因此需要找到类似的结构。转换时我会看这个。

我创建了一个用户定义的函数getOptions(),以返回每个make的选项。每辆车的价值来自第一个下拉列表中可能的项目列表。我循环这些可能的值,使用该函数返回该make的选项列表,并将这些列表作为值添加到字典results,其键是汽车的make。同样,对于R,需要找到具有与python字典类似功能的对象。

列表字典需要转换为数据帧,该数据帧包括转置操作以使汽车制造的标题输出整齐,并且每个标题下方的列包含关联的模型。

整个事情最后都可以写成csv。

因此,希望这能让您了解实现您想要的一种方法。也许其他人可以使用它来帮助为您写一个解决方案。

Python演示如下:

import requests
from bs4 import BeautifulSoup as bs
import re
import ast
import pandas as pd

headers = {
    'User-Agent' : 'Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.102 Safari/537.36'
}


def getOptions(make):  #function to return options based on make
    data = {
             'search[filter_enum_make]': make,
             'search[dist]' : '5',
             'search[category_id]' : '29'
            }

    r = requests.post('https://www.otomoto.pl/ajax/search/list/', data = data, headers = headers)   
    try:
        # verify the regex here: https://regex101.com/r/emvqXs/1
        data = re.search(r'"filter_enum_model":(.*),"new_used"', r.text ,flags=re.DOTALL).group(1) #regex to extract the string containing the models associated with the car make filter 
        aDict = ast.literal_eval(data) #convert string representation of dictionary to python dictionary
        d = len({k.lower(): v for k, v in aDict.items()}.keys()) #find length of unique keys when accounting for case
        dirtyList = list(aDict)[:d] #trim to unique values
        cleanedList = [item for item in dirtyList if item != 'other' ] #remove 'other' as doesn't appear in dropdown
    except:
        cleanedList = [] # sometimes there are no associated values in 2nd dropdown
    return cleanedList

r = requests.get('https://www.otomoto.pl/osobowe/')
soup = bs(r.content, 'lxml')
values = [item['value'] for item in soup.select('#param571 option') if item['value'] != '']

results = {}
# build a dictionary of lists to hold options for each make
for value in values:
    results[value] = getOptions(value) #function call to return options based on make

# turn into a dataframe and transpose so each column header is the make and the options are listed below
df = pd.DataFrame.from_dict(results,orient='index').transpose()

#write to csv
df.to_csv(r'C:\Users\User\Desktop\Data.csv', sep=',', encoding='utf-8-sig',index = False )

csv输出样本:

enter image description here


示例为alfa-romeo的样本json:


alfa-romeo的正则表达式匹配示例:

{"145":1,"146":1,"147":218,"155":1,"156":118,"159":559,"164":2,"166":39,"33":1,"Alfasud":2,"Brera":34,"Crosswagon":2,"GT":89,"GTV":7,"Giulia":251,"Giulietta":378,"Mito":224,"Spider":24,"Sportwagon":2,"Stelvio":242,"alfasud":2,"brera":34,"crosswagon":2,"giulia":251,"giulietta":378,"gt":89,"gtv":7,"mito":224,"spider":24,"sportwagon":2,"stelvio":242}

使用make参数值alfa-romeo从函数调用返回的过滤器选项列表示例:

['145', '146', '147', '155', '156', '159', '164', '166', '33', 'Alfasud', 'Brera', 'Crosswagon', 'GT', 'GTV', 'Giulia', 'Giulietta', 'Mito', 'Spider', 'Sportwagon', 'Stelvio']

提琴手请求示例:

enter image description here


包含选项的ajax响应html示例:

<section id="body-container" class="om-offers-list"
        data-facets='{"offer_seek":{"offer":2198},"private_business":{"business":1326,"private":872,"all":2198},"categories":{"29":2198,"161":953,"163":953},"categoriesParent":[],"filter_enum_model":{"145":1,"146":1,"147":219,"155":1,"156":116,"159":561,"164":2,"166":37,"33":1,"Alfasud":2,"Brera":34,"Crosswagon":2,"GT":88,"GTV":7,"Giulia":251,"Giulietta":380,"Mito":226,"Spider":25,"Sportwagon":2,"Stelvio":242,"alfasud":2,"brera":34,"crosswagon":2,"giulia":251,"giulietta":380,"gt":88,"gtv":7,"mito":226,"spider":25,"sportwagon":2,"stelvio":242},"new_used":{"new":371,"used":1827,"all":2198},"sellout":null}'
        data-showfacets=""
        data-pagetitle="Alfa Romeo samochody osobowe - otomoto.pl"
        data-ajaxurl="https://www.otomoto.pl/osobowe/alfa-romeo/?search%5Bbrand_program_id%5D%5B0%5D=&search%5Bcountry%5D="
        data-searchid=""
        data-keys=''
        data-vars=""

没有正则表达式的替代版本的函数:

from bs4 import BeautifulSoup as bs

def getOptions(make):  #function to return options based on make
    data = {
             'search[filter_enum_make]': make,
             'search[dist]' : '5',
             'search[category_id]' : '29'
            }

    r = requests.post('https://www.otomoto.pl/ajax/search/list/', data = data, headers = headers)   
    soup = bs(r.content, 'lxml')
    data = soup.select_one('#body-container')['data-facets'].replace('null','"null"')
    aDict = ast.literal_eval(data)['filter_enum_model'] #convert string representation of dictionary to python dictionary
    d = len({k.lower(): v for k, v in aDict.items()}.keys()) #find length of unique keys when accounting for case
    dirtyList = list(aDict)[:d] #trim to unique values
    cleanedList = [item for item in dirtyList if item != 'other' ] #remove 'other' as doesn't appear in dropdown
    return cleanedList

print(getOptions('alfa-romeo'))

R转换和改进的python:

转换为R时,我发现了一种从服务器上的js文件中提取参数的更好方法。如果您打开开发工具,则可以看到源选项卡中列出的文件。

R(待改进):

library(httr)
library(jsonlite)

url <- 'https://www.otomoto.pl/ajax/jsdata/params/'
r <- GET(url)
contents <- content(r, "text")

data <- strsplit(contents, "var searchConditions = ")[[1]][2]
data <- strsplit(as.character(data), ";var searchCondition")[[1]][1]

source <- fromJSON(data)$values$'573'$'571'
makes <- names(source)

for(make in makes){
  print(make)
  print(source[make][[1]]$value)
  #break
 }

蟒蛇:

import requests
import json
import pandas as pd

r = requests.get('https://www.otomoto.pl/ajax/jsdata/params/')
data = r.text.split('var searchConditions = ')[1]
data = data.split(';var searchCondition')[0]
items = json.loads(data)
source = items['values']['573']['571']
makes = [item for item in source]

results = {}

for make in makes:
    df = pd.DataFrame(source[make]) ## build a dictionary of lists to hold options for each make
    results[make]  = list(df['value'])

dfFinal = pd.DataFrame.from_dict(results,orient='index').transpose()  # turn into a dataframe and transpose so each column header is the make and the options are listed below

mask = dfFinal.applymap(lambda x: x is None) #tidy up None values to empty strings https://stackoverflow.com/a/31295814/6241235
cols = dfFinal.columns[(mask).any()]

for col in dfFinal[cols]:
    dfFinal.loc[mask[col], col] = ''
print(dfFinal)

0
投票

@QHarr感谢您的回答,但是我仍然有解决它的问题,我认为你的解决方案对我很复杂:(。我试图关注你并修改我的代码:

brand <- read_html("https://www.otomoto.pl/osobowe/volkswagen/")
modelsv2 <- html_nodes(brand, xpath = '//*[@id="param573"]/option') %>%
  html_text()

modelsv2

但结果如下:

字符(0)

当我直接指向使用XPath的“模型”时,我不确定为什么没有什么?

在我使用“param571”的同时:

brand <- read_html("https://www.otomoto.pl/osobowe/volkswagen")
modelsv2 <- html_nodes(brand, xpath = '//*[@id="param571"]/option') %>%
  html_text()

modelsv2   

我得到一个完整的品牌列表,它的工作完美,所以我有点迷失为什么它不适用于模型。

© www.soinside.com 2019 - 2024. All rights reserved.