当前位置: 首页 > 科技观察

使用Python抓取网站的初学者指南

时间:2023-03-13 17:29:23 科技观察

获得使用基本Python工具抓取完整HTML网站的实践经验。有很多好书可以帮助你学习Python,但谁会真正阅读这些大部头的书呢?(剧透:反正不是我)。很多人觉得指导性书籍很有用,但我通常不会从头到尾阅读一本书来学习。我通过做一个项目来学习,试着弄清楚一些东西,然后再读另一本书。所以,暂时放下书本,让我们一起学习Python。以下是我的第一个Python抓取项目的指南。它只需要很少的Python和HTML知识。本文旨在讲解如何使用Python的requests库访问网页内容,以及使用BeatifulSoup4库和JSON、pandas库解析网页内容。我将简要介绍Selenium库,但不会深入探讨如何使用该库-一个值得单独教程的主题。最后,我希望向您展示一些提示和技巧,以减少您在网络抓取过程中遇到的问题。安装依赖项本指南的所有资源都可以在我的GitHub存储库中找到。如果您在安装Python3时需要帮助,请查看我们的Linux、Windows和Mac教程。$python3-mvenv$sourcevenv/bin/activate$pipinstallrequestsbs4pandas如果你更喜欢使用JupyterLab,你可以使用笔记本来运行所有代码。有很多方法可以安装JupyterLab,这是其中之一:#从与上面相同的虚拟环境,运行:$pipinstalljupyterlab为网站抓取项目设置一个目标现在我们已经安装了依赖项,但还需要什么完成抓取网页?让我们退后一步,确保目标明确。以下是一个成功的网络抓取项目的要求列表:我们收集的信息值得我们为构建一个有效的网络爬虫而付出的努力。我们下载的信息可以被网络爬虫合法合乎道德地收集。了解如何在HTML代码中查找目标信息。利用正确的工具:在本例中,使用了BeautifulSoup库和请求库。了解(或愿意学习)如何解析JSON对象。足够的pandas数据操作技能。关于HTML的注释:HTML是运行Internet的野兽,但我们最需要了解的是标签的工作原理。标签是一对用尖括号括起来的关键字(通常成对出现,其内容在两个标签之间)。例如,这里有一个名为pro-tip的假标签:所有你必须知道的...)。本教程将进一步介绍如何查找和访问标签。要了解有关HTML基础知识的更多信息,请查看这篇文章。在Web抓取项目中寻找什么与其他数据相比,通过Web抓取可以更好地捕获一些数据。以下是我对合适项目的指导方针:没有可用于数据(处理)的公共API。通过API抓取结构化数据会容易得多,(因此没有API)将有助于澄清收集数据的合法性和道德规范。拥有相当数量的结构化数据,以规则的、可重复的格式,证明了努力是值得的。网页抓取可能很痛苦。BeautifulSoup(bs4)使操作更简单,但不能避免网站的个别特殊性,需要定制。不需要相同的数据格式,但它确实使事情变得更容易。“边缘情况”(偏离规范)越多,爬行变得越复杂。免责声明:我没有接受过法律培训;以下内容并非正式的法律建议。关于合法性,访问大量有价值的信息可能令人兴奋,但仅仅因为它可能并不意味着应该这样做。值得庆幸的是,有一些公共信息可以指导我们的道德准则和网络抓取工具。大多数网站都有一个与站点关联的robots.txt文件,该文件指示允许哪些爬行活动,哪些不允许。它主要用于与搜索引擎交互(网络抓取的最终形式)。但是,网站上的大部分信息都被视为公共信息。出于这个原因,有些人将robots.txt文件视为一组建议,而不是具有法律约束力的文件。robots.txt文件不涉及数据收集和使用等主题。在开始抓取项目之前,请问自己以下问题:我是否在抓取版权材料?我的抓取活动会泄露个人隐私吗?我是否发送了大量可能使服务器过载或损坏服务器的请求?爬行会泄露不属于我的知识产权吗?是否有管理网站使用的服务条款,我是否遵守这些条款?我的爬行活动会降低原始数据的价值吗?(例如,我是否计划按原样重新打包数据,或者可能从原始来源提取网站流量)?当我抓取一个网站时,我确保我可以对所有这些问题回答“否”。要深入了解这些法律问题,请参阅Krotov和Silva的《Web 爬取的合法性和道德性》以及Sellars的《二十年 Web 爬取和计算机欺诈与滥用法案》,2018年。现在要抓取网站经过上述评估,我想出了一个项目。我的目标是抓取爱达荷州所有FamilyDollar商店的地址。这些商店在农村地区都很大,所以我想知道有多少家。起点是FamilyDollar的位置页面,IdahoFamilyDollar的位置页面首先,让我们在Python虚拟环境中加载先决条件。此处的代码将添加到Python文件(如果需要名称,则为scraper.py)或在JupyterLab的单元格中运行。importrequests#用于从bs4发出标准html请求importBeautifulSoup#解析html数据的神奇工具importjson#用于解析来自pandas的数据importDataFrameasdf#用于数据组织的首要库接下来,我们从目标URL请求数据。page=requests.get("https://locations.familydollar.com/id/")soup=BeautifulSoup(page.text,'html.parser')BeautifulSoup将HTML或XML内容转换为复杂的树对象。以下是我们将使用的一些常见对象类型。BeautifulSoup-解析后的内容Tag-标准HTML标记,这是你会遇到的bs4元素的主要类型NavigableString-标签内的文本字符串Comment-当我们查看requests.get()输出时,一种特殊类型的NavigableString,有更多的问题需要考虑。我只使用page.text()将请求的页面转换为可读内容,但还有其他输出类型:page.text()文本(最常见)page.content()逐字节输出page.json()JSON对象page.raw()原始套接字响应(对您不起作用)我只在使用拉丁字母的简单英语网站上执行此操作。请求中的默认编码设置可以很好地解决这个问题。然而,除了纯英文网站,还有更大的互联网世界。为了保证请求正确解析内容,可以设置文本的编码:page=requests.get(URL)page.encoding='ISO-885901'soup=BeautifulSoup(page.text,'html.parser')仔细研究BeautifulSoup标签,我们看到:bs4元素标签捕获了一个HTML标签。它有一个名称和一个属性,可以像字典一样访问:tag['someAttribute']。如果标签有多个同名属性,则只访问第一个实例。子标签可以通过tag.contents访问。所有标签后代都可以通过tag.contents访问。您始终可以使用以下字符串:re.compile("your_string")来访问字符串的所有内容,而不是导航HTML树。确定如何提取内容警告:此过程可能令人沮丧。网站抓取期间的提取可能是一个充满陷阱的艰巨过程。我认为解决这个问题的最好方法是从一个有代表性的例子开始,然后扩展(这个原则适用于任何编程任务)。查看页面的HTML源代码很重要。有很多方法可以做到这一点。您可以在终端中使用Python查看页面的完整源代码(不推荐)。运行此代码需要您自担风险:print(soup.prettify())虽然打印出页面的整个源代码可能适用于某些教程中显示的玩具示例,但大多数现代网站的页面上都有大量内容。即使是404页面也可能充满页眉、页脚等代码。通常,在您最喜欢的浏览器中通过“查看页面源代码”浏览源代码最简单(右键单击,然后选择“查看页面源代码”)。这是查找所需内容的最可靠方法(稍后我将解释原因)。FamilyDollarPageSourceCode在这种情况下,我需要在这个巨大的HTML海洋中找到我的目标内容——地址、城市、州和邮政编码。通常只需简单搜索页面源(ctrl+F)即可为您提供目标位置所在的位置。一旦我真正看到我的目标内容示例(至少一家商店的地址),我就会找到一个属性或标签来区分该内容与其他内容。首先,我需要收集爱达荷州FamilyDollar商店中不同城市的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')foriindollar_tree_list[:2]:print(i)有趣的是,我发现搜索特定类通常是一种成功的方法。通过找出对象的类型和长度,我们可以了解更多有关该对象的信息。type(dollar_tree_list)len(dollar_tree_list)可以使用.contents从BeautifulSoup“结果集”中提取内容。这也是创建单个代表性示例的好时机。example=dollar_tree_list[2]#arepresentativeexampleexample_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=[]#在dollar_tree_list中为i初始化空列表:cont=i.contents[0]href=cont['href']city_hrefs.append(href)#检查以确保iincity_hrefs[:2]:print(i)的输出是要获取爱达荷州FamilyDollar商店的URL列表。也就是说,我仍然没有得到地址信息!现在需要爬取每个城市的URL来获取这些信息。所以我们从一个有代表性的例子开始。page2=requests.get(city_hrefs[2])#再次建立代表示例soup2=BeautifulSoup(page2.text,'html.parser')FamilyDollar地图和代码地址信息嵌套在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)在内容中,有一个key叫address,需要地址信息在一个比较小的嵌套字典中。可以这样获取:arco_address=arco_json['address']arco_address嗯,注意了。现在我可以遍历存储爱达荷州URL的列表:locs_dict=[]#为city_hrefs中的链接初始化空列表:locpage=requests.get(link)#请求页面信息locsoup=BeautifulSoup(locpage.text,'html.parser')#解析页面内容locinfo=locsoup.find_all(type="application/ld+json")#提取特定元素loccont=locinfo[1].contents[0]#从bs4元素中获取内容setlocjson=json.loads(loccont)#转换为jsonlocaddr=locjson['address']#获取地址locs_dict.append(locaddr)#addaddresstolist用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)我们做到了!IdahoFamilyDollar商店有一个逗号分隔的列表。多么激动人心。关于Selenium和数据抓取的一些注意事项Selenium是一种用于自动与网页交互的常用工具。为了解释为什么它有时是必要的,让我们看一个使用Walgreens网站的例子。“InspectElement”提供了浏览器显示的代码:WalgreensLocationPageandCode而“ViewPageSource”提供了请求将获得的代码:WalgreensSourceCode如果这两个不匹配,有插件可以修改源代码code-因此,页面应该在加载到浏览器后才能访问。requests不能做到这一点,但Selenium可以。Selenium需要网络驱动程序来检索内容。事实上,它打开一个网络浏览器并收集该页面的内容。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-4col-lg-4col-md-4')对于FamilyDollar案例,我不需要Selenium,但当呈现的内容与源代码不同时,我会继续使用Selenium。总结总之,当使用站点抓取进行有意义的任务时:如果您对答案感到好奇,请耐心查阅手册(它们非常有帮助):FamilyDollar位置图美国有许多FamilyDollar商店。完整的源代码是:importrequestsfrombs4importBeautifulSoupimportjsonfrompandasimportDataFrameasdfpage=requests.get("https://www.familydollar.com/locations/")soup=BeautifulSoup(page.text,'html.parser')#查找所有状态链接state_list=soup.find_all(class_='itemlist')state_links=[]foriinstate_list:cont=i.contents[0]attr=cont.attrshrefs=attr['href']state_links.append(hrefs)#查找所有城市链接city_links=[]forlinkinstate_links:page=requests.get(link)soup=BeautifulSoup(page.text,'html.parser')familydollar_list=soup.find_all(class_='itemlist')对于familydollar_list中的商店:cont=store.contents[0]attr=cont.attrscity_hrefs=attr['href']city_links.append(city_hrefs)#获取单个商店链接store_links=[]forcity_links中的链接:locpage=requests。获取(链接)locsoup=BeautifulSoup(locpage.text,'html.parser')locinfo=locsoup.find_all(type="application/ld+json")foriinlocinfo:loccont=i.contents[0]locjson=json.loads(loccont)try:store_url=locjson['url']store_links.append(store_url)除了:pass#getaddressandgeolocationinformationstores=[]forstoreinstore_links:storepage=requests.get(store)storesoup=BeautifulSoup(storepage.text,'html.parser')storeinfo=storsoup.find_all(type="application/ld应用程序/ld+json")foriinstoreinfo:storecont=i.contents[0]storejson=json.loads(storecont)try:store_addr=storejson['address']store_addr.update(storejson['geo'])stores.append(store_addr)除了:pass#finaldataparsingstores_df=df.from_records(stores)stores_df.drop(['@type','addressCountry'],axis=1,inplace=True)stores_df['Store']="FamilyDollar"df.to_csv(stores_df,"family_dollar_locations.csv",sep=",",index=False)