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

时间:2019-03-26 23:52:28

标签: r web-scraping rvest

我正在尝试基于汽车方面制作一个具有分析功能的交互式仪表板。我希望用户能够选择宝马,奥迪等汽车品牌,并且基于此选择,他将只能选择宝马/奥迪等车型。选择每个品牌后出现问题,我无法取消属于该品牌的型号。我要从其抓取的页面:
主页-> 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”。

2 个答案:

答案 0 :(得分:2)

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

有些人可能不赞成解决方案语言是Python,但这是为了给您一些指导。我已经很长时间没有写R了,所以Python更快了。如果我有时间和意志,编辑:现在添加了R脚本

概述:

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

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

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

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

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

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

该列表字典需要转换为一个数据帧,其中包括一个转置操作,以使整车输出标题(汽车制造商)以及每个标题下方的列,其中包含相关的模型。

整个内容可以最后写入csv。

因此,希望能为您提供实现所需目标的一种方法。也许其他人可以用它来帮助您编写解决方案。

下面的Python演示:

make

csv输出示例:

enter image description here


示例作为alfa-romeo的示例json:


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

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 )

使用参数值为 make 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}

提琴手请求的样本:

enter image description here


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

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

不带正则表达式的功能的替代版本:

<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=""

R转换和改进的python:

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

R(有待改进):

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'))

Python:

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
 }

答案 1 :(得分: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   

我可以获得完整的品牌列表,而且效果很好,所以我有点迷失为什么不适用于模型。