抓取网页数据工具(有人将robots.txt文件视为一组建议.py文件 )

优采云 发布时间: 2022-01-22 23:11

  抓取网页数据工具(有人将robots.txt文件视为一组建议.py文件

)

  关于合法性,获得大量有价值的信息可能会令人兴奋,但仅仅因为它是可能的,并不意味着它应该。

  值得庆幸的是,有一些公共信息可以指导我们的道德规范和网络抓取工具。大多数 网站 都有一个与 网站 关联的 robots.txt 文件,指示哪些抓取活动是允许的,哪些是不允许的。它主要用于与搜索引擎交互(网络抓取的最终形式)。但是,网站 上的大部分信息都被视为公共信息。因此,有些人将 robots.txt 文件视为一组建议,而不是具有法律约束力的文件。robots.txt 文件不涉及诸如合乎道德的数据采集和使用等主题。

  在开始爬取项目之前,请先问自己以下问题:

  当我抓取 网站 时,请确保我可以对所有这些问题回答“否”。

  如需深入了解这些法律问题,请参阅 Krotov 和 Silva 于 2018 年出版的 The Legality and Ethics of Web Scraping,以及 Sellars 的 20 Years of Web Scraping and Computer Fraud and Abuse Act。

  现在开始抓取 网站

  经过上述评估,我想出了一个项目。我的目标是爬取爱达荷州所有 Family Dollar 商店的地址。这些商店在农村地区很大,所以我想知道有多少。

  起点是 Family Dollar 位置页面

  

  爱达荷州家庭美元地点页面

  首先,让我们在 Python 虚拟环境中加载先决条件。此处的代码将添加到 Python 文件(如果需要名称,则为 scraper.py)或在 JupyterLab 单元中运行。

  import requests # for making standard html requestsfrom bs4 import BeautifulSoup # magical tool for parsing html dataimport json # for parsing datafrom pandas import DataFrame as df # premier library for data organization

  接下来,我们从目标 URL 请求数据。

  page = requests.get("https://locations.familydollar.com/id/")soup = BeautifulSoup(page.text, 'html.parser')

  BeautifulSoup 将 HTML 或 XML 内容转换为复杂的树对象。以下是我们将使用的一些常见对象类型。

  当我们查看 requests.get() 输出时,还有更多需要考虑。我只使用 page.text() 将请求的页面转换为可读内容,但还有其他输出类型:

  我只对使用拉丁字母的纯英语 网站 进行操作。请求中的默认编码设置适用于此。然而,除了纯英语 网站 之外,还有更大的互联网世界。为确保请求正确解析内容,您可以设置文本的编码:

  page = requests.get(URL)page.encoding = 'ISO-885901'soup = BeautifulSoup(page.text, 'html.parser')

  仔细查看 BeautifulSoup 标签,我们看到:

  确定如何提取内容

  警告:此过程可能令人沮丧。

  网站抓取过程中的提取可能是一个令人生畏和被误解的过程。我认为解决这个问题的最好方法是从一个有代表性的例子开始,然后扩展(这个原则适用于任何编程任务)。查看页面的 HTML 源代码很重要。有很多方法可以做到这一点。

  您可以在终端中使用 Python 查看页面的完整源代码(已弃用)。运行此代码需要您自担风险:

  print(soup.prettify())

  虽然打印出页面的整个源代码可能适用于某些教程中显示的玩具示例,但大多数现代 网站 页面上都有大量内容。即使是 404 页面也可能充满代码,如页眉、页脚等。

  通常,通过“查看页面源代码”(右键单击并选择“查看页面源代码”)在您喜欢的浏览器中浏览源代码是最简单的。这是查找目标内容的最可靠方法(稍后我会解释原因)。

  

  家庭美元页面源代码

  在这种情况下,我需要在这片巨大的 HTML 海洋中找到我的目标内容——地址、城市、州和邮政编码。通常,在页面源 (ctrl+F) 上进行简单搜索即可找到目标位置。一旦我真正看到目标内容的示例(至少一个商店的地址),我就会找到一个属性或标签来区分该内容与其他内容。

  首先,我需要在爱达荷州的 Family Dollar 商店采集不同城市的 URL,并访问这些 网站 以获取地址信息。这些 url 似乎都收录在 href 标记中。奇妙!我将尝试使用 find_all 命令进行搜索:

  dollar_tree_list = soup.find_all('href')dollar_tree_list

  搜索 href 没有结果,该死的。这可能会失败,因为 href 嵌套在 itemlist 类中。对于您的下一次尝试,请搜索 item_list。由于 class 是 Python 中的保留字,请改用 class_。soup.find_all() 原来是 bs4 函数的瑞士*敏*感*词*。

  dollar_tree_list = soup.find_all(class_ = 'itemlist')for i in dollar_tree_list[:2]:  print(i)

  有趣的是,我发现为特定类搜索方法通常是一种成功的方法。我们可以通过找出对象的类型和长度来了解有关对象的更多信息。

  type(dollar_tree_list)len(dollar_tree_list)

  可以使用 .contents 从 BeautifulSoup“结果集”中提取内容。这也是创建一个具有代表性的示例的好时机。

  example = dollar_tree_list[2] # a representative exampleexample_content = example.contentsprint(example_content)

  使用 .attr 查找此对象内容中存在的属性。注意: .contents 通常返回一个精确的项目列表,所以第一步是使用方括号表示法对项目进行索引。

  example_content = example.contents[0]example_content.attrs

  现在,我可以看到 href 是一个属性,可以像字典项一样提取:

  example_href = example_content['href']print(example_href)

  集成网站爬虫

  所有这些探索都为我们提供了前进的道路。这是一个清理版本,澄清了上面的逻辑。

  city_hrefs = [] # initialise empty list for i in dollar_tree_list:    cont = i.contents[0]    href = cont['href']    city_hrefs.append(href) #  check to be sure all went wellfor i in city_hrefs[:2]:  print(i)

  输出是在爱达荷州抓取 Family Dollar 商店的 URL 列表。

  也就是说,我仍然没有得到地址信息!现在,需要抓取每个城市的 URL 以获取此信息。因此,我们用一个有代表性的例子重新开始这个过程。

  page2 = requests.get(city_hrefs[2]) # again establish a representative examplesoup2 = BeautifulSoup(page2.text, 'html.parser')

  

  家庭美元地图和代码

  地址信息嵌套在 type="application/ld+json" 中。在进行了大量的地理位置抓取之后,我开始意识到这是存储地址信息的通用结构。幸运的是,soup.find_all() 支持按类型搜索。

  arco = soup2.find_all(type="application/ld+json")print(arco[1])

  地址信息在第二个列表成员中!我明白!

  使用 .contents 提取(从第二个列表项)内容(这是过滤后的适当默认操作)。同样,由于输出是一个列表,我索引列表项:

  arco_contents = arco[1].contents[0]arco_contents

  哦,看起来不错。此处提供的格式与 JSON 格式一致(并且该类型的名称中确实收录“json”)。JSON 对象的行为类似于具有嵌套字典的字典。一旦你习惯了它,它实际上是一种很好的格式(嗯,它比一长串正则表达式命令更容易编程)。尽管在结构上它看起来像一个 JSON 对象,但它仍然是一个 bs4 对象,需要以编程方式转换为 JSON 对象才能访问它:

  arco_json =  json.loads(arco_contents)

  type(arco_json)print(arco_json)

  在那个内容中,有一个地址键叫做地址键,它要求地址信息在一个相对较小的嵌套字典中。可以这样检索:

  arco_address = arco_json['address']arco_address

  嗯,请注意。现在我可以遍历存储 Idaho URL 的列表:

  locs_dict = [] # initialise empty list for link in city_hrefs:  locpage = requests.get(link)   # request page info  locsoup = BeautifulSoup(locpage.text, 'html.parser')      # parse the page's content  locinfo = locsoup.find_all(type="application/ld+json")      # extract specific element  loccont = locinfo[1].contents[0]        # get contents from the bs4 element set  locjson = json.loads(loccont)  # convert to json  locaddr = locjson['address'] # get address  locs_dict.append(locaddr) # add address to list

  使用 Pandas 组织我们的 网站 抓取结果

  我们在字典中加载了很多数据,但是还有一些额外的无用项使得重用数据比需要的更加复杂。要执行最终的数据组织,我们需要将其转换为 Pandas 数据框,删除不需要的列 @type 和 country,并检查前五行以确保一切正常。

  locs_df = df.from_records(locs_dict)locs_df.drop(['@type', 'addressCountry'], axis = 1, inplace = True)locs_df.head(n = 5)

  确保保存结果!!

  df.to_csv(locs_df, "family_dollar_ID_locations.csv", sep = ",", index = False)

  我们做到了!所有 Idaho Family Dollar 商店都有一个逗号分隔的列表。多么激动人心。

  关于 Selenium 和数据抓取的一点说明

  Selenium 是用于自动与网页交互的常用工具。为了解释为什么有时需要这样做,让我们看一个使用 Walgreens 网站 的示例。“Inspect Element”提供了浏览器显示内容的代码:

  

  沃尔格林位置页面和代码

  虽然“查看页面源代码”提供了有关请求将获得什么的代码:

  

  沃尔格林源代码

  如果两者不匹配,则存在可以修改源代码的插件 - 因此应该在页面加载到浏览器后访问它。requests 不能这样做,但 Selenium 可以。

  Selenium 需要 Web 驱动程序来检索内容。实际上,它会打开一个 Web 浏览器并采集该页面的内容。Selenium 功能强大 - 它可以通过多种方式与加载的内容进行交互(阅读文档)。使用 Selenium 获取数据后,继续像以前一样使用 BeautifulSoup:

  url = "https://www.walgreens.com/storelistings/storesbycity.jsp?requestType=locator&state=ID"driver = webdriver.Firefox(executable_path = 'mypath/geckodriver.exe')driver.get(url)soup_ID = BeautifulSoup(driver.page_source, 'html.parser')store_link_soup = soup_ID.find_all(class_ = 'col-xl-4 col-lg-4 col-md-4')

  对于 Family Dollar 的情况,我不需要 Selenium,但是当呈现的内容与源代码不同时,我会继续使用 Selenium。

  概括

  总之,当使用 网站 抓取有意义的任务时:

  如果您对答案感到好奇:

  

  家庭美元位置图

  美国有很多 Family Dollar 商店。

  完整的源代码是:

  import requestsfrom bs4 import BeautifulSoupimport jsonfrom pandas import DataFrame as df page = requests.get("https://www.familydollar.com/locations/")soup = BeautifulSoup(page.text, 'html.parser') # find all state linksstate_list = soup.find_all(class_ = 'itemlist') state_links = [] for i in state_list: cont = i.contents[0] attr = cont.attrs hrefs = attr['href'] state_links.append(hrefs) # find all city linkscity_links = [] for link in state_links: page = requests.get(link) soup = BeautifulSoup(page.text, 'html.parser') familydollar_list = soup.find_all(class_ = 'itemlist') for store in familydollar_list: cont = store.contents[0] attr = cont.attrs city_hrefs = attr['href'] city_links.append(city_hrefs)# to get individual store linksstore_links = [] for link in city_links: locpage = requests.get(link) locsoup = BeautifulSoup(locpage.text, 'html.parser') locinfo = locsoup.find_all(type="application/ld+json") for i in locinfo: loccont = i.contents[0] locjson = json.loads(loccont) try: store_url = locjson['url'] store_links.append(store_url) except: pass # get address and geolocation informationstores = [] for store in store_links: storepage = requests.get(store) storesoup = BeautifulSoup(storepage.text, 'html.parser') storeinfo = storesoup.find_all(type="application/ld+json") for i in storeinfo: storecont = i.contents[0] storejson = json.loads(storecont) try: store_addr = storejson['address'] store_addr.update(storejson['geo']) stores.append(store_addr) except: pass # final data parsingstores_df = df.from_records(stores)stores_df.drop(['@type', 'addressCountry'], axis = 1, inplace = True)stores_df['Store'] = "Family Dollar" df.to_csv(stores_df, "family_dollar_locations.csv", sep = ",", index = False)

0 个评论

要回复文章请先登录注册


官方客服QQ群

微信人工客服

QQ人工客服


线