整合营销服务商

电脑端+手机端+微信端=数据同步管理

免费咨询热线:

使用Python进行网页抓取的介绍

网页抓取是一种重要的技术,经常在许多不同的环境中使用,尤其是数据科学和数据挖掘。 Python在很大程度上被认为是网络抓取的首选语言,其原因在于Python的内嵌电池特性。 使用Python,您可以在大约15分钟内使用不到100行代码创建一个简单的抓取脚本。 因此,无论何种用途,网页抓取都是每个Python程序员必须具备的技能。

在我们开始动手之前,我们需要退后一步,考虑什么是网页抓取,什么时候应该使用它,何时避免使用它。

如您所知,网页抓取是一种用于从网站自动提取数据的技术。 重要的是要理解,网页抓取是一种从各种来源(通常是网页)中提取数据的粗略技术。 如果网站的开发人员足够慷慨地提供API来提取数据,那么访问数据将是一种更加稳定和健壮的方式。 因此,根据经验,如果网站提供API以编程方式检索其数据,请使用它。 如果API不可用,则只能使用网络抓取。

请务必遵守有关您使用的每个网站的网页抓取的任何规则或限制,因为有些网站不允许这样做。 有了这个清楚的认识,让我们直接进入教程。

在本教程中,我们将抓取http://quotes.toscrape.com/,这是一个列出着名作家名言的网站。

网页抓取管道

我们可以将web-scraping理解为包含3个组件的管道:

下载:下载HTML网页

解析:解析HTML并检索我们感兴趣的数据

存储:以特定格式将检索到的数据存储在本地计算机中

下载HTML

从网页中提取任何数据,从逻辑上讲,我们首先要下载它。 我们有两种方法可以做到这一点:

1.使用浏览器自动化库

您可以使用Selenium等浏览器自动化库从网页下载HTML。 Selenium允许您打开浏览器,比方说Chrome,并根据需要控制它。 您可以在浏览器中打开网页,然后使用Selenium自动获取该页面的HTML代码。

但是,这种方法有一个很大的缺点 - 它明显变慢。 原因是运行浏览器并在浏览器中呈现HTML的开销。 此方法仅应用于特殊情况 - 我们要抓取的内容在浏览器中使用JavaScript代码,或者要求我们单击按钮/链接以获取数据,Selenium可以为我们执行此操作。

2.使用HTTP库

与第一种方法不同,HTTP库(例如Requests模块或Urllib)允许您发送HTTP请求,完全不需要打开任何浏览器。 这种方法应该始终是首选,因为它比Selenium快得多。

现在让我告诉您如何使用Selenium和Requests库实现管道这个组件:

使用Requests

使用以下命令安装Requests模块:

现在您可以在代码中使用它,如下所示:

这里,对URL进行HTTP GET请求,这几乎与下载网页同义。 然后,我们可以通过访问requests.get方法返回的结果对象来获取页面的HTML源代码。

使用Selenium

您可以通过pip安装selenium模块:

在这里,我们首先创建一个表示浏览器的webdriver对象。 这样做会在运行代码的计算机上打开Chrome浏览器。 然后,通过调用webdriver对象的get方法,我们可以打开URL。 最后,我们通过访问webdriver对象的page_source属性来获取源代码。

在这两种情况下,URL的HTML源都作为字符串存储在page变量中。

解析HTML和提取数据

不必深入计算机科学理论,我们可以将解析定义为分析字符串的过程,以便我们可以理解其内容,从而轻松访问其中的数据。

在Python中,有两个库可以帮助我们解析HTML:BeautifulSoup和Lxml。 Lxml是一个比BeautifulSoup更低级的框架,我们可以在BeautifulSoup中使用Lxml作为后端,因此对于简单的HTML解析,BeautifulSoup将是首选的库。

但在我们深入分析之前,我们必须分析网页的HTML,看看我们想要抓取的数据是如何构建和定位的。只有当我们掌握了这些信息时,我们才能从解析的HTML中获取我们想要的信息。但幸运的是,我们不必在编辑器中打开源代码,并手动理解每个HTML元素并将其与渲染页面中的相应数据相关联。大多数浏览器都提供了一个检查器,比如Chrome的开发人员工具,它使我们只需单击它们即可快速查看任何元素的HTML代码。

要在Chrome中执行此操作,请在Chrome中打开网页,然后右键单击要抓取的数据,然后选择“检查”。在Firefox中,此选项称为Inspect Element - 这是在做相同的事情,但只是名称不同。

您会注意到Chrome窗口底部打开了一个窗格,其中包含您单击的元素的源代码。 浏览一下源代码,了解我们想要抓取的数据是如何在HTML代码中构建的。

经过一些检查后你可以理解,http://quotes.toscrape.com/上的每个引用都包含在一个带有class =“quote”属性的div中。 在该div中,引用的文本在class =“text”的范围内,作者的名称在class =“author”的小标签中。 当我们实际解析HTML并提取数据时,将需要此信息。

现在,让我们开始使用BeautifulSoup解析HTML页面。 但首先,我们必须安装它:

安装好之后,可以像下面这样在代码中调用:

首先,我们通过将页面传递给BeautifulSoup类构造函数来创建页面的解析版本。 如您所见,我们还将第二个参数html.parser传递给构造函数。 这是Beautiful Soup将用于解析传递给它的字符串的解析器的名称。 你可以使用我们之前谈到过的解析器lxml,因为你已经安装了Lxml库。

然后,我们提取包含class =“quote”的页面中的所有div标签,因为我们知道这些是包含引用的div。 为此,Beautiful Soup 4提供了find_all功能。 我们将标记名称和类名称传递给find_all函数,并返回满足条件的所有标记,即包含引用的标记。

这里需要注意的一件重要事情是,我们在这里使用树结构。 变量soup以及引用的每个元素都是树。 在某种程度上,引用的元素是较大的soup树的一部分。 无论如何,为避免进入不同的讨论,让我们继续。

我们知道引用的文本是带有class =“text”的span标记,而作者是带有class =“author”的小标记。 要从quote元素中提取它们,我们再次使用类似的函数find。 find函数使用与find_all函数相同的参数。 唯一的区别是它返回满足条件的第一个标记,而find_all返回标记列表。 此外,我们希望访问返回对象的text属性,该对象包含该标记中包含的文本。

因此,正如您在代码中看到的那样,我们遍历列表引用的所有元素,并提取引用文本和作者名称,将它们存储在名称为scraped的列表。 在控制台上打印时,已抓取的列表如下所示:

存储检索的数据

一旦我们获得了数据,我们就可以以任何我们想要的格式存储它,例如CSV文件,SQL数据库或NoSQL数据库。 严格来说,这一步不应算作抓取过程的一部分,但为了完整起见,我将简要介绍它。

我想说最流行的存储抓取数据的方法是将它们存储为CSV电子表格,所以我将简要介绍如何做到这一点。 我不会详细介绍,因为您应该参考官方的Python文档。 所以,不用多说,让我们看看代码。

我们可以看到,代码非常明显。 我们从打开的quotes.csv文件创建一个CSV编写器对象,然后使用writerow函数逐个写入引用。 很明显,writerow函数接受一个列表作为输入,然后将其作为一行写入CSV。

结论和后续步骤

本教程应该帮助您了解在学习自己实现简单的scraper时基本上是什么。 这种抓取应该足以实现简单的自动化或小规模数据检索。 但是如果你想有效地提取大量数据,你应该研究一下抓取框架,特别是Scrapy。 它可以帮助您使用几行代码编写非常快速,高效的scraper。 无论你使用什么样的框架,在那个闪亮的表面下面,框架也使用这些非常基本的抓取原则,所以理解本教程应该可以帮助你为开始抓取的探险建立基础知识。

英文原文:https://stackabuse.com/introduction-to-web-scraping-with-python/
译者:javylee

文检索与消息队列中间件

在前面的章节中 我们学习了构建一个分布式系统所必需的各种基本知识和技能 比如分布式系统的基础理论、网络编程技术、 RP 架构、内存计算 分布式文件系统、分布式计算框架等,但仅仅掌握这些内容还是远远不够的 我们还需要学习和掌握分布式系统中常用的一些中间件 这些中间件主要用于分布式系统中常见的一些业务场景 数据全文检索、日志和消息处理、数据库的分片、网站的负载均衡等。由于篇幅有限, 本章只对全文检索与消息队列这两个用途广泛又相对复杂的中间件进行全面介绍。

全文检索

我们已经习惯以网上搜索的方式来快速学习知识并解决技术问题了,这就需要互联网搜索引擎。如何在海量网页(文本)信息中准确且快速地找到包含我们所搜索的关键字的所有网页并合理排序展示,的确是一个很有挑战的难题。

除了我们日常工作使用的搜索引擎,大量互联网应用都需要具备关键字检索(即全文检索)功能。要理解关键字检索的价值,我们需要先了解关系型数据库索引的局限性。我们在SQL查询语句中使用like "%keyword%"这种查询条件时,数据库的索引是不起作用的。此时,搜索就变成类似于一页页翻书的遍历过程了,几乎全部都是IO操作,因此对性能的负面影响很大;如果需要对多个关键词进行模糊匹配,比如 like"%keyword1%" and like "%keyword2%",此时的查询效率也就可想而知了。

关键字检索在本质上是将一系列文本文件的内容以“词组(关键词)”为单位进行分析并生成对应的索引记录。索引存储了关键词到文章的映射关系,在映射关系中记录了关键词所在的文章编号、出现次数、出现频率等关键信息,甚至包括了关键词在文章中出现的起始位置于是我们有机会看到关键字“高亮展示”的查询结果页面。

关键字检索的第一步是对整个文档(Document)进行分词,得到文本中的每个单词,这对于英文来说毫无困难,因为一个英文语句中的单词乙间是通过空格子付大杰力开B,但十人口句中的字与词是两个概念,所以中文分词就成了一个很大的问题,比如利. 北尔人女目如何分词呢?是“北京、天安门”还是“北、京、天安、门”﹖解决这个问题的最好办法是将中文词库结合中文分词法,其中比较知名的中文分词法有IK(IKAnalyzer)或庖丁(PaodingAnalyzcr),配合开源Lucene使用起来非常方便。

Lucene

Java生态圈中有名的全文检索开源项目是 Apache Lucene(后简称Lucene),它在2001年成为Apache的开源项目。Lucene最初的贡献者Doug Cutting是全文检索领域的一位资深专家,曾经是V-Twin搜索引擎(苹果的Copland操作系统的成就之一)的主要开发者,他贡献Lucene的目的是为各种中小型应用程序加入全文检索功能。目前Apache官方维护的Lucene相关的开源项目如下。

  • Lucene Core:用Java编写的核心类库,提供了全文检索功能的底层API 与SDK。
  • Solr:基于Lucene Core开发的高性能搜索服务,提供了RESTAPI的高层封装接口,还提供了一个 Web管理界面。
  • PyLucene:一个 Python版的Lucene Core的高仿实现。

为了对一个文档进行索引,Lucene提供了5个基础类,分别是Document、Field、Index Writer、Analyzer和 Directory。首先,Document用来描述任何待搜索的文档,例如HTML页面、电子邮件或文本文件。我们知道,一个文档可能有多个属性,比如一封电子邮件有接收日期、发件人、收件人、邮件主题、邮件内容等属性,每个属性都可以用一个Field对象来描述。此外,我们可以把一个Document对象想象成数据库中的一条记录,而每个Field对象就是这条记录的个字段。其次,在一个 Document 能被查询之前,我们需要对文档的内容进行分词以找出文档包含的关键字,这部分工作由 Analyzer对象实现。Analyzer把分词后的内容交给IndexWriter建立索引。IndexWriter是Lucene用来创建索引(Index)的核心类之一,用于把每个Document对象都加到索引中来,并且把索引对象持久化保存到Directory中。Directory代表了Lucene索引的存储位置,目前有两个实现:第1个是 FSDirectory,表示在文件系统中存储;第2个是RAMDirectory,表示在内存中存储。

在明白了建立 Lucene索引所需要的这些类后,我们就可以对任意文档创建索引了。下面给出了对指定文件目录下的所有文本文件建立索引的源码:

//索引文件目录
Directory indexDir = FSDirectory.open (Paths.get ("index-dir"));Analyzer analyzer = new StandardAnalyzer();
IndexWriterConfig config = new IndexWriterConfig(analyzer);IndexWriter indexWriter = new Indexwriter (indexDir, config);//需要被索引的文件目录
String dataDir=". ";
File[] dataFiles = new File(dataDir).listFiles();long startTime - new Date() .getTime();
for(int i= 0; i<dataFiles.length; i++){
if(dataFiles[i].isFile () && dataFiles[i].getName ().endsWith(".txt"))(
System .out.println ("Indexing file"+
dataFiles[i]-getCanonicalPath());
Reader txtReader = new FileReader (dataFiles[i]);Document doc - new Document();
//文档的文件名也被作为一个Field,从而定位到具体的文件
doc.add(new StringField("filename", dataFiles[i].getName (),
Field.store.YES));
doc.add(new TextField("body", txtReader));indexwriter.addDocument (doc);
}
}
indexWriter.close();
long endTime - new Date( .getTime();
System.out.println("It takes " +(endTime - startTime)
+ " milliseconds to create index for the files in directory "+dataDir);

你可以把包含英文句子的任意文本(比如英文歌词)都放到项目的根目录下,运行上面的程序完成索引的创建过程,如果一切正常,则会出现类似于如下所示的提示:

Indexing file D:\project\leader-study-search\lemon-tree.txt
It takes 337 milliseconds to create index for the files in directory .

接下来我们尝试查询关键字,查询内容(对应 body字段)包括“good”的所有文档并输出结果。为此,我们首先需要打开指定的索引文件,然后构造Query对象并执行查询逻辑,最后输出查询结果。下面是对应的源码:

//打开指定的索引文件
Directory indexDir = FSDirectory.open (Paths.get("index-dir"));IndexReader reader = DirectoryReader.open (indexDir);
IndexSearcher searcher = new IndexSearcher(reader);//查询
String querystr = "good";
Analyzer analyzer = new StandardAnalyzer();
QueryParser parser = new QueryParser ( "body", analyzer);Query a- parser.parse(querystr);
int hitsPerPage = 10;
TopDocs docs=searcher.search(q, hitsPerPage);ScoreDoc[] hits = docs.scoreDocs;
//输出查询结果
System.out.println("Found " +hits.length + " hits.");for (int i =0;i< hits.length; ++i){
int docid = hits[i] .doc;
Document d = searcher.doc(docId);
System.out.println((i +1)+ "." +d.get ("filename "));
}

如果你搜索的关键字恰好在某个文本文件中,则运行这段代码,控制台会输出类似于下面的内容:

Found 1 hits.1. lemon-tree.txt

通过对上面例子的学习,我们已经初步掌握了Lucene 的基本用法,Lucene编程的整个流程如下图所示。

Lucene编程的整个流程可以总结为如下三个独立步骤。

  • 建模:根据被索引文档(原始文档)的结构与信息,建模对应的 Document对象与相关的 Lucene 的索引字段(可以有多个索引),这一步类似于数据库建模。关键点之一是确定原始文档中有哪些信息需要作为Field存储到Document对象中。通常文档的ID或全路径文件名是要保留的(Field.Store.YES),以便检索出结果后让用户查看或下载原始文档。
  • 收录:编写一段程序扫描每个待检索的目标文档,将其转换为对应的 Document对象并且创建相关索引,最后存储到Lucene的索引仓库(Directory)中。这一步可以被类比为初始化数据(批量导入数据)。
  • 检索:使用类似于SQL查询的Lucene API来编写我们的全文检索条件,从Lucene的索引仓库中查询符合条件的 Document并且输出给用户,这一步完全类似于SQL查询.

Lucene还普遍与网络爬虫技术相结合,提供基于互联网资源的全文检索功能,比如有不少提供商品比价和最优购物的信息类网站,通过爬虫去抓取各个电商平台上的商品信息并将其录入Lucene索引库里,然后提供用户检索服务,如下所示为此类系统的一个典型架构图。

Solr

如果把Lucene与MySQL做对比,你会发现Lucene像MySQL的某个存储引擎,比如InnoDB或者MyISAM。Lucene只提供了基本的全文检索相关的API,还不是一个独立的中间件,功能不够丰富,API也比较复杂,不太方便使用。除此之外,Lucene还缺乏一个更为关键的特性—分布式,当我们要检索的文档数量特别庞大时,必然会遇到宕机的瓶颈,所以有了SolrElasticSearch,它们都是基于Lucene的功能丰富的分布式全文检索中间件。

如下所示是Solr的架构示意图。我们看到,Solr在 Lucene的基础上开发了很多企业级增强功能:提供了一套强大的Data Schema来方便用户定义document的结构;增加了高效灵活的缓存功能;增加了基于Web的管理界面以提供集中的配置管理功能;可以将Solr的索引数据分片存储到多个节点上,并且通过多副本复制的方式来提升系统的可靠性。

Solr的分布式集群模式也被称为SolrCloud,这是一种很灵活的分布式索引和检索系统。SolrCloud也是一种具有去中心化思想的分布式集群,在集群中并没有特殊的 Master 节点,而是依靠ZooKeeper来协调集群。SolrCloud中一个索引数据(Collection)可以被划分为多个分片(Shard)并存储在不同的节点上(Solr Core或者Core),在索引数据分片的同时,SolrCloud也可以实现分片的复制(Replication)功能以提升集群的可用性。SolrCloud集群的所有状态信息都被放在ZooKeeper中统一维护,客户端在访问SolrCloud集群时,首先要向ZooKeeper查询索引数据(Collection)所在的Core节点的地址列表,然后就可以连接到任意Core 节点上来完成索引的所有操作(CRUD)了。

如下图所示给出了一个 SolrCloud 参考部署方案,本方案中的索引数据(Collection)被分为两个分片,同时每个 Shard分片的数据都有3份,其中一份所在的Core节点被称为Leader,其他两个Core节点被称为Replica。所有索引数据都分布在8个Core 中,他们位于独立的3台服务器上,所以其中任何一台机器宕机,都不会影响到系统的可用性。如果某个服务器在运行中宕机,那么SolrCloud 会自动触发Leader的重新选举行为,这是通过ZooKeeper提供的分布式锁功能来实现的。

之前说到,SolrCloud中的每个Shard分片都是由一个Leader 与N个Replica组成的,而且客户端可以连接到任意一个Core节点上进行索引数据的操作,那么,此时索引数据是如何实现多副本同步的呢?下图给出了背后的答案。

如果客户端连接的Core不是Leader,则此节点会把请求转发给所在Shard分片的Leader节点。

Leader 会把数据(Document)路由到所在Shard分片的每个Replica节点。如果文档分片路由规则计算出目标Shard分片是另外一个分片,则 Leader会把数据转发给该分片对应的Leader节点去处理。

接下来谈谈另外一个重要问题,这个问题就是SolrCloud采用了什么算法进行索引数据的分片Shard?为了选择合适的分片算法,SolrCloud提出了以下两个关键要求。

(1)分片算法的计算速度必须快,因为在建立索引及访问索引的过程中都频繁用到分片算法。

(2)分片算法必须保证索引数据能均匀地分布到每一个分片上,SolrCloud 的查询是先分后总的过程,如果某个分片中的索引文档(Document)的数量远大于其他分片,那么在查询此分片时所花的时间就会明显多于其他分片,也就是说最慢分片的查询速度决定了整体的查询速度。

基于以上两点,SolrCloud选择了一致性哈希算法来实现索引分片。

本节最后说说SolrCloud 所支持的“近实时搜索”这个高级特性。近实时搜索就是在较短的时间内使得新添加的 Document可见可查,这主要基于Solr的 Soft Commit机制。在8.1.2节中讲到,Lucene在创建索引时数据是在提交时被写入磁盘的,这就是Hard Commit,它确保了即便停电也不会丢失数据,但会增加延时。同时,对于之前已经打开的Searcher 来说,新加入的Document也是不可见的。Solr为了提供更实时的检索能力,提供了Soft Commit的新模式,在这种模式下仅把数据提交到内存,此时并没有将其写入磁盘索引文件中,但索引Index可见,Solr 会打开新的Searcher 从而使新的 Document可见。同时,Solr会进行预热缓存及查询以使得缓存的数据也是可见的。为了保证数据最终会被持久化保存到磁盘上,可以每1~10分钟自动触发Hard Commit而每秒钟自动触发Soft Commit.Soft Commit也是一把双刃剑,一方面Commit越频繁,查询的实时性越高,但同时增加了Solr 的负荷,因为Commit越频繁,越会生成小且多的索引段(Segment),于是Solr Merge 的操作会更加频繁。在实际项目中建议根据业务的需求和忍受度来确定Soft Commit 的频率。

ElasticSearch

ElasticSearch(后简称ES)并不是 Apache出品的,它与Solr类似,也是基于Lucene的一个分布式索引服务中间件。ES 的出现晚于Solr,但从当前的发展状况来看,它的势头和流行度要超过前辈许多。值得一提的是在日志分析领域,以ES为核心的ELK三件套(ELK Stack)成为事实上的标准。ELK其实并不是一款软件,而是一整套解决方案,是三款软件即ES、Logstash和Kibana首字母的缩写。这三款软件都是开源软件,通常配合使用,又先后归于Elastic.co 公司名下,故被简称为ELK Stack。在 Google上有文章提到,ELK Stack每个月的下载量达到50万次,已经成为世界上最流行的日志管理平台,而在当前流行的基于Docker 与 Kubernetes 的PaaS平台上,ELK也是标配之一,非 Apache 出品的ES之所以能后来居上,与ELK的流行和影响力也有着千丝万缕的联系。

实际上,在所有分布式系统中最需要全文检索的就是日志模块了。如果尝试对节点数超过5个的分布式系统做Trouble Shooting,你就会明白日志集中收集并提供全文检索功能的重要性和紧迫性了。在没有类似于ELK Stack这样一套日志子系统的情况下,我们不得不登录每个主机来查询日志,并且“拼接”所有相关的查询结果,以定位和分析故障出现的环节及前因后果,这项工作看起来并不复杂,但实际上很耗费精力,因为在每个主机上都可能有多个日志文件需要分析,仅仅定位某个时间点的日志就让人很头疼了。

如下所示是ELK Stack 的一个架构组成图。Logstash是一个有实时管道能力的数据收集引擎,用来收集日志数据并且作为索引数据写入ES集群中,我们也可以开发自定义的日志采集探头并按照ELK的日志索引格式写入ES集群中;Kibana则为ES提供了数据分析及数据可视化的Web平台,它可以在ES的索引中查找数据并生成各种维度的表图。

ES通过简单的RESTful API来隐藏Lucene的复杂性,从而让全文搜索变得简单,它提供了近实时的索引、搜索、分析等功能。我们可以这样理解和描述ES。

  • 分布式的实时文档存储,文档中的每个字段都可被索引并搜索。
  • 分布式的实时分析搜索引擎。
  • 可以扩展到上百台服务器,处理PB级的结构化或非结构化的数据。

在ES中增加了Type这个概念,如果我们把Index类比为Database,Type就相当于Table,但这个比喻不是很恰当,因为我们知道不同 Table 的结构完全不同,而一个Index中所有Document 的结构是高度一致的。ES 中的Type其实是Document 中的一个特殊字段,用来在查询时过滤不同的 Document,比如我们在做一个 B2C的电商平台时,需要对每个店铺的商品进行索引,则可以用Type区分不同的商铺。实际上,Type的使用场景非常少,这是我们需要注意的。

与SolrCloud一样,ES也是分布式系统,但ES并没有采用ZooKeeper作为集群的协调者,而是自己实现了一套被称为Zen Discovery 的模块,该模块主要负责集群中节点的自动发现和Master节点的选举。Master节点维护集群的全局状态,比如节点加入和离开时进行Shard的重新分配,集群的节点之间则使用P2P的方式进行直接通信,不存在单点故障的问题。ES不使用ZooKeeper 的一个好处是系统部署和运维更加简单了,坏处是可能出现所谓的脑裂问题。要预防脑裂问题,我们需要重视的一个参数是 discovery.zen.minimum_master_nodes,它决定了在选举 Master节点的过程中需要有多少个节点通信,一个基本原则是这里需要设置成N/2+1,N是集群中节点的数量。例如在一个三节点的集群中,minimum_master_nodes应该被设为3/2+1 =2(四舍五入),当两个节点的通信失败时,节点1会失去它的主状态,同时节点2不会被选举为Master节点,没有一个节点会接收索引或搜索的请求,没有一个分片会处于不一致状态。ES在Zen Discovery算法上做了不少改进以解决脑裂问题,GitHub上关于脑裂的Issue后来在2014年被关闭了,如下所示是相关说明:

2. Zen Discovery
Pinging after master loss (no local elects)Fixes the split brain issue:#2488
Batching join requests
More resilient joining process (wait on a publish from master)

ES 的集群与SolrCloud还有一个重大差别,即ES集群中的节点类型不止一种,有以下几种类型。

  • Master节点:它有资格被选为主节点,控制整个集群。
  • Data节点:该节点保存索引数据并执行相关操作,例如增删改查、搜索及聚合。
  • Load balance节点:该节点只能处理路由请求、处理搜索及分发索引操作等,从本质上来说该节点的表现等同于智能负载平衡器。Load balance节点在一个较大的集群中是非常有用的,Load balance节点在加入集群后可以得到集群的状态,并可以根据集群的状态直接路由请求。
  • Tribe节点:这是一个特殊的Load balance节点,可以连接多个集群,在所有连接的集群上都执行搜索和其他操作。
  • Ingest节点:是 ES 5.0新增的节点类型,该节点大大简化了以往ES集群中添加数据的复杂度。

如下所示是Tribe节点连接多个ES集群展示日志的ELK部署方案,据说魅族就采用了这种方案来解决各个IDC机房日志的集中展示问题。

本节最后,我们一起安装ES并编写一些简单的例子来加深对ES 的理解并初步掌握其API的用法。我们可以去ES官网下载 ES 的二进制版本(在Windows上运行时可以下载ZIP包),解压后在其 bin目录下有可执行的脚本,比如 elasticsearch.bat。在执行启动脚本后,在浏览器里访问http://localhost:9200,会显示如下信息,表明ES启动正常:

{
"name" : "Y8 klCx",
"cluster name" :"elasticsearch",
"cluster uuid" :"X3tmO4iXSKa8l_ ADWNh_g","version" :{
"number" :"5.3.0",
"build hash" :"3adb13b",
"build date" : "2017-03-23T03:31:50.652Z","build snapshot" :false,
"lucene version" :"6.4.1",
},
"tagline" :"You Know, for Search"
}

ES提供了REST接口以让我们很方便地将一个Document加入索引中,为了学会这个API首先,我们需要知道在ES中一个Document由以下3个字段唯一确定。

  • _index:文档所在的索引。
  • _type:文档的类型。
  • _id:文档的字符串ID,可以在插入文档时自己指定,也可以让ES自己随机生成。

现在我们就容易理解Document CRUD的REST接口的URL的写法了: http:/localhost:9200/<index>/<type>/[<id>]。

此外,在ES中一个Document是用JSON结构体来表示的,由于JSON的结构本身就有字段类型的暗示,比如字符串与数字的属性是用不同方式表示的,因此 ES可以实现JSO到Document Schema t日以刻凤ES被称为Schema-less 系统的原因。但Schema-less i进个的方你想要的字段类型映别,心T配的Schema不满意,则也可以使用自定义映射(MappTgO nNo Schema,如果对ES自动匹配的Schema不满意,则也可以使用自定义映射(Mapping)的方式来设计更为合理的Schema。

从5.0版本开始,ES开发了一个全新的Java客户端API,这个API的最大目标是移除对ES 及 Lucene类库的依赖,变得更加轻量级,同时采用了分层设计的思路,底层仅仅包括一个HTTP通信层及一个Sniffer用于发现集群中的其他节点,其他层则包括Query DSL等功能,我们在本节中就使用这个新的Java API来完成Document的操作。

我们需要在Maven中引用这个API:

<dependency>
<groupId>org.elasticsearch.client</groupId><artifactId>sniffer</artifactId>
<version>5.3.0</version>
</dependency>

下面这段代码的作用是获取ES的健康信息,类似于我们在浏览器中访问地址http://localhost:9200/_cluster/health:

RestClient client = RestClient
.builder(new HttpHost("localhost",9200)).build();
Response response = client.performRequest(
"GET", "/ cluster/health", Collections.singletonMap ("pretty"
"true"));
HttpEntity entity =response.getEntity();
System. out.println(EntityUtils.tostring(entity));

在运行后会输出下面这样一段内容,其中 status属性比较重要,green表示集群很健康:

"cluster name" :"elasticsearch",
"status" :"green",
"timed out" : false,"number of nodes" :1,
"number of_data_nodes" :1,
"active_primary_shards" :0,"active shards" :0,
"relocating_shards" :0,"initializing_shards" :0,"unassigned shards" :0,
"delayed unassigned_shards" :0,"number of pending tasks" :0,"number of in_flight_fetch" :0,
"task max _waiting_in_queue_millis" :0,"active_shards percent as_number" :100.0
}

接下来,我们在名为 blogs 的 Index里插入一个 Document,Document 的 type为 blogId为1。下面是Document的JSON内容:

{
"user" : "Leader us",
"post date" :"2017-12-12",
"message" : "Mycat 2.0 is coming ! "
}

对应的代码如下:

//index a document
String docJson= "{An"+
" "user\ " :\"Leader us\ ",n"+
" "post datel ": "2017-12-12\",n" +
" \"messaael" :\ "Mycat 2.0 is coming!\"\n"+"}";
System. out.println(docJson);
entity = new NStringEntity(docJson,ContentType.APPLICATION_JSON);Response indexResponse = client.performRequest("PUT","/blogs/blog/1",
Collections.singletonMap ( "pretty", "true"), entity);
System.out.println (Entityotils.tostring (indexResponse.getEntity()));

运行上述代码后,在ES中成功插入一个索引文档,控制台会输出如下信息:

{
" index" :"blogs",
"_type" : "blog","_id" :"1",
"_version" :1,"result" : "created"," _shards" :{
"total" :2,
"successful" :1,
"failed" :0
},
"created" : true
}

如果第2次运行上面的代码,则输出信息中的result值会从created变为updated,表明是更新Document 的操作,同时_version会累加。我们用下面的代码继续增加100个用于测试的Document:

String[] products= {"Mycat ", "Mydog", "Mybear","MyAllice"};
for (int i=0;i<100;i++)
{
//index a document
String docJson= "{\n"+
"\ "user\ ":\ "Leader us ",\n" +
\"post_date\" :\""+(2017+i)+"-12-12\",\n"+
"\ "messagel" :\""+products[i%products.length] +i+" is
coming! "\n" +
"]";
HttpEntity entity = new NStringEntity(docJson, ContentType.APPLICATIONM_ JSOtResponse indexResponse = client.performRequest ("PUT","/blos/blog/"+i,
Collections.singletonMap ("pretty", "true"),
entity);
System.out.println(EntityUtils.toString(indexResponse.getEntity()));
}
此时打开浏览器,输入查询指令:
http://localhost:9200/blogs/blog/_search?pretty=true

则会出现下面的查询信息:

{
"took" :14,
"timed_ out" :false," shards" :{
"total":5,
"successful" :5,"failed" :0
),
"hits" :{
"total" :100,
"max_score" : 1.0,"hits" :[
{
" index" :"blogs"," type" :"blog",
"id" : "19"," score" :1.0," source":{
"user" : "Leader us",
"postdate" :"2036-12-12",
"message" :"MyAllice19 is coming ! "
},
{
" index" : "blogs","type" : "blog" ," id" :"22"," score" :1.0," source" :{
"user":"Leader us",
"post date" :"2039-12-12",
"message" :"Mybear22 is coming!"
}
},

根据上述信息,我们得知 blogs这个Index的分片数量为5个,hits部分为匹配查询条件的Document列表,总共有100个符合条件的文档,_source部分为我们录入的原始Document的信息。如果查询某个特定Document的内容,则只要在URL中指定文档的ID即可,比如http://localhost:9200/blogs/blog/1。

如果我们要查询包含某个关键字的文档,则该怎么办?ES提供了一个Query DSL 的语法,采用JSON格式描述,用起来也比较方便,比如下面这段DSL 语句表明查询任意字段值中包含mycat这个关键字的Document:

{
"query":{
"query string": {
" query" :"mycat"
}
}
}

我们只要将上述DSL作为JSON内容Post到某个索引的URL地址即可,下面是具体的代码:

//search document
String dsl- "{"query":{"+
"\ " query string \": {"+
" "auery ": \ "mycat\""+
"] ]}";
System.out.println (dsl);
HttpEntity entity = new NStringEntity(dsl,
ContentType.APPLICATION_JSON);
Response response =
client.performRequest("POST", "/blogs/blog/search",
Collections.singletonMap ("pretty", "true"),entity);
System.out.println (EntityUtils.toString (response.getEntity()));

在这里就不再深入讨论ES的其他编程内容了,我们主要围绕Query DSL的语法细节进行讲解,比如高亮显示匹配结果、过滤查询结果、控制结果集缓存及联合查询等内容。

本文给大家讲解的内容是架构解密从分布式到微服务:对全文检索中间件的全面讲解

  1. 下篇文章给大家讲解的是架构解密从分布式到微服务:消息队列中间件;
  2. 觉得文章不错的朋友可以转发此文关注小编;
  3. 感谢大家的支持!

据观世界

网页信息爬取已经成为现在科学研究获取数据的重要方式之一,也是基于大数据的人工智能技术发展的基础之一。在本教程中,我们将介绍如何自动从网站获取数据。大多数网站都是以受众为中心创建的,您可以使用搜索引擎或在网络浏览器中输入网址,来查看页面上显示的信息。有时候,我们希望自动提取和处理这些数据,这时就需要网络抓取帮助我们免于无聊的重复劳动。我们可以创建自定义爬虫程序来访问网站,提取特定数据并以特定方式处理这些数据。

本文将仅演示从bbc.com新闻网站中提取新闻数据,但您应该能够通过一些试验和纠错来调整它以从任何您想要的网站中提取信息。

例如,您可能需要:

  • 从每周在线发布的报告中提取数据
  • 获取你最喜欢的运动队的日程时间表
  • 找到您喜爱类型的所有即将上映的电影的发布日期
  • 在网站有更新时获取自动通知

Web抓取还有许多其他用例。但是,您还应注意,版权法和网络抓取法律很复杂,因国家/地区而异。虽然人们通常不介意你复制他们的内容甚至是为了商业利益进行网络信息爬取,但是有一些法律案件涉及了从LinkedIn抓取数据、从OKCupid抓取数据等,网络抓取可能会违反法律、违反特定网站的服务条款或违反道德准则。所以在抓取信息之前,要确定其是否合法。

概要与环境

在本教程中我们将介绍:

  • 网站到底是什么以及HTML如何运作
  • 在Web浏览器中查看HTML
  • 使用Python下载网页
  • 使用BeautifulSoup提取数据

我们将使用在线编程环境repl.it,因此您无需在本地安装任何软件即可逐步进行操作。如果您想根据自己的需要对本教程中的代码进行调整,您可以在repl.it按照注册流程创建一个免费帐户。

网页:美女与野兽

毫无疑问,我们都使用过Web浏览器访问过网页。网站有两种形式:

  • 您习惯使用的那个,您可以在其中看到文本,图像和其他媒体。不同的字体,大小和颜色用于以有用和美学方式显示信息。
  • 网页源码。这是一个计算机代码,可以告诉您的Web浏览器(例如Mozilla Firefox或Google Chrome)显示什么以及如何显示它。

网站通常使用三种计算机语言进行编码:HTML,CSS和JavaScript。这本身就是一个庞大而复杂的领域,基本了解其中的一些工作原理是有效进行自动化网页抓取的必要条件。您在浏览器中打开任何网站并右键单击网站的某个页面,您将看到一个菜单,其中应包含"查看源"选项。

如下图所示:左侧是普通网页和打开的页面右键菜单(通过右键单击页面显示)。单击查看源码按钮会生成右侧的结果,我们可以看到包含Web浏览器显示完整页面所需的所有数据和支持信息的代码。虽然左边的页面易于阅读和使用,并且看起来很好,但右边的页面则看起来像是怪物,一般人较难理解,但是如果我们想编写自定义Web抓取器,了解它是必要的。

网页与源码对比

使用源代码页面"查找"功能

首先要弄清楚这两个页面的对应方式:正常显示的网站的哪些部分与代码的哪些部分相匹配。您可以在源代码视图中使用"查找"(Ctrl + F)来查找在普通视图中可见的特定文本片段以帮助完成此操作。在左边的文章中,我们可以看到以"Getting a TV job"这个短语开头。 如果我们在代码视图中搜索此短语,我们可以在代码中找到相应的文本,在第805行。

源码查找

高亮显示部分之前的<p class ="story-body__introduction">是HTML代码,用于指定段落(HTML中的<p>)从此处开始,并且这是一种特殊的段落(故事简介)。该段继续到</p>符号。您不必完全理解HTML,但您应该知道它包含的构成新闻文章的文本数据和有关如何显示文章的其他数据。网页抓取的很大一部分工作是查看网页代码,以识别我们感兴趣的数据,并将其与其他代码分开。

在大多数页面中,有很多代码用于定义网页的结构,布局,交互性和其他功能,而相对较少的代码包含我们通常查看的实际文本和图像。对于特别复杂的页面,即使在查找功能的帮助下,也很难找到负责页面特定部分的代码。为了解决这个问题,大多数Web浏览器都带有所谓的"开发人员工具",主要针对程序员协助创建和维护Web站点,但这些工具对于Web抓取也非常有用。

使用开发人员工具

您可以从主菜单打开浏览器的开发者工具,左侧显示Google Chrome,右侧显示Mozilla Firefox。 如果您使用的是其他网络浏览器,应该能够找到类似的设置。

开发者工具

打开开发人员工具会在Web浏览器中显示一个新面板,通常位于底部或右侧。 该工具包含一个"Inspector"面板和一个选择器工具,可以通过按下下面的红色图标来选择。 选择器工具激活后,您可以单击网页的某些部分以查看相应的源代码。 在下图中,我们在普通视图中选择了相同的第一段,我们可以在下面的面板中再次看到<p class = story-body__introduction">代码。

Selector

开发人员工具比使用简单的源代码查找工具更强大,但它们也更复杂。 您应该根据自己的经验和尝试分析的页面的复杂性选择一种方法。

使用Python获取网页

现在我们已经看到了关于如何在浏览器中构建网页的内容,我们可以开始使用Python检索和操作它们。 Python不是Web浏览器,因此我们不会通过Python获得网页的"正常"表示。 相反,我们只能检索和操作HTML源代码。

我们将使用requests库通过一个Python Repl执行此操作。打开repl.it并选择创建一个新的Python repl。

repl.it

这将带您进入一个可以编写和运行Python代码的Python编译环境。首先,我们将从BBC新闻主页下载内容,并打印出HTML源代码的前1000个字符。

您可以使用以下四行Python来完成此操作:

import requests

url = "https://bbc.com/news"
response = requests.get(url)
print(response.text[:1000])

将此代码放在Repl自动为您创建的main.py文件中,然后按"Run"按钮。在短暂的延迟之后,您便可在输出窗格中看到输出的HTML源代码,类似于我们在上面的Web浏览器中查看的内容。

获取网页

代码分析:

  • 在第1行中,我们导入了Python的requests库,这是一个发出Web请求的库。
  • 在第3行中,我们定义了一个包含BBC新闻网站的URL的变量。您可以在Web浏览器中访问此URL以查看BBC新闻主页。
  • 在第4行中,我们将我们定义的URL传递给requests.get函数,该函数将访问URL指向的网页并获取HTML源代码。我们将其加载到一个名为response的新变量中。
  • 在第5行中,我们访问响应对象的text属性,该属性包含所有HTML源代码。我们只获取前1000个字符,并将它们传递给print函数,该函数只是将生成的文本转储到输出窗格。

我们现在已经自动爬取了一个网页,并可以显示部分内容。我们不太可能对网页的完整源代码转储感兴趣(除非我们出于存档的原因存储它),所以让我们提取页面的一些符合我们需要的部分而不是前1000个字符。

使用BeautifulSoup提取所有URL

我们现在想在BBC新闻主页上找到所有新闻文章,并获取他们的网址。如果我们查看下面的主页面,我们会看到主页上有很多故事,使用"检查"工具将光标置于任何标题之上,我们可以看到每个故事都有一个唯一的网址,它将我们带到那个新闻报道。例如,在下图中标题为"US and Canada agree new trade deal"的新闻的网址是https://www.bbc.com/news/business-45702609。

如果我们使用浏览器的开发人员工具检查该元素,我们可以看到它是一个<a>元素,它是链接的HTML,其中<href>标签指向URL。请注意,href部分仅指向URL的后半部分,省略了https://www.bbc.com部分。由于我们已经在BBC上,因此该网站可以使用相对URL而不是绝对URL。这意味着当您单击该链接时,您的浏览器将确定该URL未完成,并在其前面加上https://www.bbc.com。

新闻URL

我们可以尝试使用Python的内置文本搜索功能(如find()或正则表达式)从BBC页面中提取所有URL,但实际上不可能可靠地执行此操作。幸运的是,有一个功能强大且易于使用的HTML解析库,名为BeautifulSoup,它将帮助我们从给定的HTML片段中提取所有链接。 我们可以通过修改Repl中的代码来使用它,如下所示:

import requests
from bs4 import BeautifulSoup

url = "https://bbc.com/news"
response = requests.get(url)
html = response.text
soup = BeautifulSoup(html, "html.parser")
links = soup.findAll("a")
for link in links:
 print(link.get("href"))

如果您运行此代码,您将看到它输出了几十个URL,每行一个。您可能会注意到代码现在比以前运行起来时间要长得多,BeautifulSoup不是内置于Python中,而是第三方模块。 这意味着在运行代码之前,Repl必须去获取此库并为您安装它。后续运行会更快。

URL获取

代码分析:

  • 在第2行,我们导入BeautifulSoup库,用于解析和处理HTML。
  • 在第9行,我们将HTML转换为"soup"。这是BeautifulSoup对网页的表示,其中包含一系列有用的编程功能,用于搜索和修改页面中的数据。我们使用"html.parser"选项来解析默认包含的HTML,BeautifulSoup还允许您在此处指定自定义HTML解析器。例如,您可以安装并指定一个更快的解析器,如果您需要处理大量HTML数据,这可能很有用。
  • 在第10行,我们在HTML中找到所有元素并将它们提取到列表中。请记住,当我们使用Web浏览器查看URL时,我们注意到HTML中的<a>元素用于定义链接,href属性用于指定链接的去向。该行找到所有HTML <a>元素。
  • 在第11行中,我们循环遍历所有链接,在第12行中我们打印出href部分。
  • 最后两行显示了BeautifulSoup的优势。在没有它的情况下尝试找到并提取这些元素真的很难,但现在我们可以用两行可读代码来完成它!

如果我们查看输出窗格中的URL,我们会看到很多结果。我们有绝对URL(以"http"开头)和相对URL(以"/"开头)。他们中的大多数都转到一般页面而不是特定的新闻文章。我们需要在我们感兴趣的链接中找到一个模式(转到新闻文章),这样我们才能只提取我们感兴趣的那些。

再次尝试和纠错是最好的方法。如果我们访问BBC新闻主页并使用开发人员工具来检查新闻文章的链接,我们会发现它们都有类似的模式。它们是以"/news"开头并以一串数字结尾的相对URL,例如/news/newsbeat-45705989。

我们可以对代码进行一些小改动,只输出与此模式匹配的URL。用以下四行替换上述代码的最后两行:

for link in links:
 href = link.get("href")
 if href.startswith("/news") and href[-1].isdigit():
 print(href)

代码运行结果:

URL筛选

获取文章

现在我们已经获取到BBC新闻主页上的每篇文章的链接,可以进一步获取这些文章每一篇的数据。作为一个实验项目,让我们从每篇文章中提取专有名词(人物,地点等)并打印出最常见的名词,以了解今天新闻所讨论的内容。

调整我们的代码如下:

import requests
import string
from collections import Counter
from bs4 import BeautifulSoup

url = "https://bbc.com/news"

response = requests.get(url)
html = response.text
soup = BeautifulSoup(html, "html.parser")
links = soup.findAll("a")
news_urls = []

for link in links:
 href = link.get("href")
 if href.startswith("/news") and href[-1].isdigit():
 news_url = "https://bbc.com" + href
 news_urls.append(news_url)
 all_nouns = []
 for url in news_urls[:10]:
 print("Fetching {}".format(url))
 response = requests.get(url)
 html = response.text
 soup = BeautifulSoup(html, "html.parser")
 words = soup.text.split()
 nouns = [word for word in words if word.isalpha() and word[0] in string.ascii_uppercase]
 all_nouns += nouns
 print(Counter(all_nouns).most_common(100))

这段代码比我们之前编写的代码更复杂,所以如果你不了解所有这些代码,请不要担心。主要变化是:

在顶部,我们添加了两个新的导入。一个用于字符串,它是标准的Python模块,包含一些有用的单词和字母快捷方式。我们将使用它来识别字母表中的所有大写字母。第二个模块是一个计数器,一旦我们建立了所有名词的列表,我们就可以在列表中找到词频最高的名词。

我们在第一个for循环的顶部添加了news_urls = []。我们将其标识为"news URL"后,不是打印出每个URL,而是将其添加到此列表中,以便我们以后可以下载每个页面。在for循环内部向下两行,我们将根域("http://bbc.com")与每个href属性组合在一起,然后将完整的URL添加到news_urls列表中。

然后我们进入另一个for循环,在那里我们遍历前10个新闻URL(如果你有更多时间,你可以删除[:10]部分来遍历所有新闻页面,但为了提高效率,我们只是用前10个)。

打印出我们正在获取的URL(因为下载每个页面需要一秒左右,显示一些反馈很好,这样我们可以看到该程序正在运行的进度)。然后我们像上文一样获取页面并将其转换为Soup。使用words = soup.text.split()进行分词。

下一行循环遍历该给定文章中的所有单词,并仅保留由数字字符组成的单词,并以大写字母开头(string.ascii_uppercase只是大写字母)。这也是一种非常粗略的提取名词的方式,我们会得到很多单词(比如句子开头的那些单词),这些单词实际上并不是专有名词,但现在又是一个很好的近似值。

最后,我们将所有看起来像名词的单词添加到我们的all_nouns列表中,然后转到下一篇文章来做同样的事情。下载解析所有页面之后,我们可以使用Python中内置的Counter来统计词频。

您应该看到类似于下图的输出(尽管您的文字会有所不同,因为新闻每隔几个小时就会发生变化)。我们有最常见的名词,然后计算这个名词出现在我们所看到的所有10篇文章中的频率。

名词获取

我们可以看到我们的原始提取和解析方法远非完美,由于每篇文章底部的社交媒体链接,大多数文章都会出现​​像"Twitter"和"Facebook"这样的词,因此它们的存在并不意味着Facebook和Twitter本身就在今天的新闻中。类似地,诸如"From"之类的单词不是名词,并且还包括诸如"BBC","Optimizely"和"Business"之类的其他单词,因为它们主要出现在页面上文章之外的文本上。

后续

我们已经学习了网页信息抓取的基础知识,并研究了网络的工作原理,如何从网页中提取信息,以及如何进行一些非常基本的文本提取。你可能想要做一些与BBC提取单词不同的东西!您可以从https://repl.it/@GarethDwyer1/beginnerwebscraping中fork此Repl并对其进行修改以更改它所搜索的站点以及它提取的内容。

好用的Python网页抓取工具推荐:

  • Scrapy:想要爬取百万甚至数十亿网页的人使用的框架。Scrapy允许您构建"蜘蛛"程序化机器人,根据您指定的规则收集数据。
  • Newspaper:我们上文谈到将在线新闻文章的主要文本与页面上的所有其他内容分开比较困难。Newspaper使用手动指定的规则和一些算法的组合来从每篇文章中删除非核心文本。
  • Selenium:现代网页的许多部分都是动态的,例如它们仅在您向下滚动页面时加载,或者单击按钮以显示更多内容。这些动态站点很难自动抓取,但是selenium允许您启动真实的Web浏览器并模拟真人操作,帮助您自动访问这种动态内容。

还有许多其他工具,只需将它们相互结合使用即可完成很多工作。如果您有好的建议或问题,请留言评论。

END