深度优先

这个家伙好懒,除了文章什么都没留下

0%

转自:https://cloud.tencent.com/developer/article/1171773

Web Scraper 是一款免费的,适用于普通用户(不需要专业 IT 技术的)的爬虫工具,可以方便的通过鼠标和简单配置获取你所想要数据。例如知乎回答列表、微博热门、微博评论、电商网站商品信息、博客文章列表等等。

环境需求

这么简单的工具当然对环境的要求也很简单了,只需要一台能联网的电脑,一个版本不是很低的 Chrome 浏览器,具体的版本要求是大于 31 ,当然是越新越好了。目前 Chrome 的已经是60多了,也就是说这个版本要求也不是很高。

安装过程

  • 在线安装方式 在线安装需要具有可FQ网络,可访问 Chrome 应用商店

1、在线访问 web Scraper 插件 ,点击 “添加至 CHROME”。

2、然后点击弹出框中的“添加扩展程序”

3、安装完成后在顶部工具栏显示 Web Scraper 的图标。

  • 本地安装方式 不能FQ的可以使用本地FQ方式,在本公众号回复「爬虫」,可下载 Chrome 和 Web Scraper 扩展插件

1、打开 Chrome,在地址栏输入 chrome://extensions/ ,进入扩展程序管理界面,然后将下载好的扩展插件 Web-Scraper_v0.3.7.crx 拖拽到此页面,点击“添加到扩展程序”即可完成安装。如图:

2、安装完成后在顶部工具栏显示 Web Scraper 的图标。

初识 web scraper

*打开 Web Scraper *

开发人员可以路过看后面了

windows 系统下可以使用快捷键 F12,有的型号的笔记本需要按 Fn+F12;

Mac 系统下可以使用快捷键 command+option+i ;

也可以直接在 Chrome 界面上操作,点击设置—>更多工具—>开发者工具

打开后的效果如下,其中绿色框部分是开发者工具的完整界面,红色框部分是 Web Scraper 区域,也就是我们之后要操作的部分。

注意:如果打开开发者工具在浏览器的右侧区域,则需要调节开发者工具位置到浏览器底部。

原理及功能说明

我们抓取数据一般都是什么场景呢,如果只是零星的几条数据或者特定的某条数据也就不值得用工具了,之所以用工具是因为要批量的获取数据,而用手工方式又太耗时费力,甚至根本不能完成。例如抓取微博热门前100条,当然可以一页一页的翻,但是实在是太耗精力,再比如说知乎某个问题的所有答案,有的热门问题回答数成千上万,手工来,还是省省吧。

基于这样的一种需求,一般可采用两种方式采集这些数据,一种叫“我们程序员的方式”,另一种叫“你们普通人的方式”。

“我们程序员的方式”是指开发人员会根据需求自己写个爬虫或者利用某个爬虫框架,盯着屏幕狂敲代码,根据需求的复杂程度,敲代码的时长从一两个小时到一两天不等,当然如果时间太长的话可能是因为需求太复杂,针对这种复杂的需求来说,普通人的方式或许也就行不通了。常用的爬虫框架 Scrapy(Python)、WebMagic(Java)、Crawler4j(Java)。

这篇还是主要介绍“你们普通人的方式”,也就是 Web Scraper 这个工具,因为其界面简单、操作简单,并且可导出 Excel 格式,不懂开发的同学也可以很快上手。而且对于一些简单的需求,开发人员也没必要自己实现个爬虫,点几下鼠标毕竟要比敲半天代码快吧。

数据爬取的思路一般可以简单概括如下:

1、通过一个或多个入口地址,获取初始数据。例如一个文章列表页,或者具有某种规则的页面,例如带有分页的列表页;

2、根据入口页面的某些信息,例如链接指向,进入下一级页面,获取必要信息;

3、根据上一级的链接继续进入下一层,获取必要信息(此步骤可以无限循环下去);

原理大致如此,接下来正式认识一下 Web Scraper 这个工具,来,打开开发者工具,点到 Web Scraper 这个标签栏,看到分为三个部分:

Create new sitemap:首先理解 sitemap ,字面意思网站地图,这里可以理解为一个入口地址,可以理解为其对应一个网站,对应一个需求,假设要获取知乎上的一个问题的回答,就创建一个 sitemap ,并将这个问题所在的地址设置为sitemap 的 Start URL,然后点击 “Create Sitemap”即可创建一个 sitemap。

Sitemaps:sitemap 的集合,所有创建过的 sitemap 都会在这里显示,并且可以在这里进入一个 sitemap 进行修改和数据抓取等操作。

Sitemap:进入某个 sitemap ,可以进行一系列的操作,如下图:

其中红色框部分 Add new selector 是必不可少的步骤。selector 是什么呢,字面意思:选择器,一个选择器对应网页上的一部分区域,也就是包含我们要收集的数据的部分。

需要解释一下,一个 sitemap 下可以有多个 selector,每个 selector 有可以包含子 selector ,一个 selector 可以只对应一个标题,也可以对应一整个区域,此区域可能包含标题、副标题、作者信息、内容等等信息。

Selectors:查看所有的选择器。

Selector graph:查看当前 sitemap 的拓扑结构图,根节点是什么,包含几个选择器,选择器下包含的子选择器。

Edit metadata:可以修改 sitemap 信息,标题和起始地址。

Scrape:开始数据抓取工作。

Export data as CSV:将抓取的数据以 CSV 格式导出。

到这里,有一个简单的认识就可以了,实践出真知,具体的操作案例才具有说服力,下面就以几个例子来说一说具体的用法。

案例实践

简单试水 hao123

由浅入深,先以一个最简单的例子为入口,只是作为进一步认识 Web Scraper 服务

需求背景:看到下面 hao123 页面中红色框住的部分了吧,我们的需求就是统计这部分区域中的所有网站名称和链接地址,最后以生成到 Excel 中。 因为这部分内容足够简单,当然真正的需求可能比这复杂,这么几个数据手工统计的时间也很快。

开始操作

1、假设我们已经打开了 hao123 页面,并且在此页面的底部打开了开发者工具,并且定位到了 Web Scraper 标签栏;

2、点击“Create Sitemap”;

3、之后输入 sitemap 名称和 start url,名称只为方便我们标记,就命名为hao123(注意,不支持中文),start url 就是hao123的网址,然后点击 create sitemap;

4、之后 Web Scraper 自动定位到这个 sitemap,接下来我们添加一个选择器,点击“add new selector”;

5、首先给这个 selector 指定一个 id,就是一个便于识别 名字而已,我这里命名为 hot。因为要获取名称和链接,所以将Type 设置为 Link,这个类型就是专门为网页链接准备的,选择 Link 类型后,会自动提取名称和链接两个属性;

6、之后点击 select ,然后我们在网页上移动光标,会发现光标所到之处会有颜色变化,变成绿色的,表示就是我么当前选择的区域。我们将光标定位到需求里说的那一栏的某个链接处,例如第一个头条新闻,在此处单击,这个部分就会变成红色,说明已经选中了,我们的目的是要选多个,所以选中这个之后,继续选第二个,我们会发现,这一行的链接都变成了红色,没错,这就是我们要的效果。然后点击”Done selecting!”,最后别忘了勾选 Multiple ,表示要采集多条数据;

7、最后保存,save selector。点击Element preview 可以预览选择的区域,点击 Data preview 可以在浏览器里预览抓取的数据。 后面的文本框里的内容,对于懂技术的同学来说很清楚,这就是 xpath,我们可以不通过鼠标操作,直接手写 xpath 也可以;

完整操作过程如下:

8、上一步操作完,其实就可以导出了。先别急,看一下其他的操作,Sitemap hao123 下的 Selector graph,可以看出拓扑结构图,_root 是根 selector ,创建一个 sitemap 自动会有一个 _root 节点,可以看到它的子 selector,就是我们创建的 hot selector;

9、Scrape ,开始抓取数据。

10、Sitemap hao123 下的 Browse ,可以通过浏览器直接查看抓取的最后结果,需要再;

11、最后,使用 Export data as CSV,以 CSV 格式导出,其中 hot 列是标题,hot-href 列是链接;

怎么样,赶紧试一下吧

抓取知乎问题所有回答

简单的介绍完了,接下来试一个有些难度的,抓取一个知乎问题的所有答案,包括回答者昵称、赞同数量、回答内容。问题:为什么鲜有炫富的程序员?
知乎的特点是,页面向下滚动才会加载后面的回答

1、首先还是在 Chrome 中打开这个链接,链接地址为:https://www.zhihu.com/question/30692237,并调出开发者工具,定位到 Web Scraper 标签栏;

2、Create new sitemap,填写 sitemap name 和 start url;

3、接下来,开始添加选择器,点击 Add new selector;

4、先来分析一下知乎问题的结构,如图,一个问题由多个这种区域组成,一个区域就是一个回答,这个回答区域包括了昵称、赞同数、回答内容和发布时间等。红色框住的部分就是我们要抓取的内容。所以我们抓取数据的逻辑是这样的:由入口页进入,获取当前页面已加载的回答,找到一个回答区域,提取里面的昵称、赞同数、回答内容,之后依次向下执行,当已加载的区域获取完成,模拟向下滚动鼠标,加载后续的部分,一直循环往复,直到全部加载完毕;

5、内容结构的拓扑图如下,_root 根节点下包含若干个回答区域,每个区域下包含昵称、赞同数、回答内容;

6、按照上面这个拓扑图,开始来创建选择器,填写 selector id 为 answer(随意填),Type 选择 Element scroll down 。解释一下:Element 就是针对这种大范围区域的,这个区域还要包含子元素,回答区域就对应 Element,因为要从这个区域获取我们所需的数据,而 Element scroll down 是说这个区域利用向下滚动的方式可以加载更多出来,就是针对这种下拉加载的情况专门设计的。

7、接下来点击 Select,然后鼠标到页面上来,让当绿色框框住一个回答区域后点击鼠标,然后移动到下一个回答,同样当绿色框框住一个回答区域后点击鼠标。这时,除了这两个回答外,所有的回答区域都变成了红色框,然后点击”Done selecting!”,最后别忘了选择 Multiple ,之后保存;

8、接下来,单击红色区域,进入刚刚创建的 answer 选择器中,创建子选择器;

9、创建昵称选择器,设置 id 为 name,Type 设置为 Text,Select 选择昵称部分,如果没经验的话,可能第一次选的不准,发现有错误,可以调整,保存即可;

10、创建赞同数选择器;

11、创建内容选择器,由于内容是带有格式的并且较长,所以有个技巧,从下面选择会比较方便;

12、执行 Scrape 操作,由于内容较多,可能需要几分钟的时间,如果是为了做测试,可以找一个回答数较少的问题做测试。

资源获取

  • 在本公众号内回复「爬虫」,获取 Chrome 和 Web Scraper 扩展程序的安装包
  • 在本公众号内回复 「sitemap」,获取本文中抓取 hao123 和知乎的 sitemap 文本 获取下来的 sitemap 是一段 json 文本,通过 Create new Sitemap 下的 Import Sitemap,然后输入获取到的 sitemap json 串,并起个名字,然后点击导入按钮即可。

自定义语音合成 可 参考:https://mp.weixin.qq.com/s/THFmz4uNpb0lNYWshaZ2qQ

合成微软语音晓晓 可 参考:https://www.cnblogs.com/viter/p/10685402.html

图灵聊天机器人API:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
public class TulingHelper
{
private const string HOST = "http://openapi.tuling123.com/openapi/api/v2";
private static readonly Logger logger = LogManager.GetCurrentClassLogger();

public static async Task<string> RequestTuling(string text)
{
try
{
using (var httpClient = new HttpClient())
{
var body = "{'reqType':0,'perception': {'inputText': {'text': '" + text + "'}},'userInfo': {'apiKey': '*****','userId': '267842'}}";
var request = new HttpRequestMessage()
{
Method = HttpMethod.Post,
RequestUri = new Uri(HOST),
Content = new StringContent(body, Encoding.UTF8)
};

var response = await httpClient.SendAsync(request);
if (response.StatusCode != System.Net.HttpStatusCode.OK)
{
Console.WriteLine("The Response {0}", response.StatusCode);
return null;
}
var responseString = await response.Content.ReadAsStringAsync();
var obj = JObject.Parse(responseString);
return (string)obj["results"][0]["values"]["text"];
}
}
catch (Exception ex)
{
logger.Error(ex, ex.Message);
return "出错了";
}
}
}

微软语音合成API:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
public class VoicesHelper
{
private static readonly Logger logger = LogManager.GetCurrentClassLogger();

private const string TOKEN_URI = "https://southeastasia.api.cognitive.microsoft.com/sts/v1.0/issuetoken";
private const string SUB_KEY = "*****";
private const string HOST = "https://southeastasia.voice.speech.microsoft.com/cognitiveservices/v1?deploymentId=***";
private const string RESOURCE_NAME = "SpeechSerivce";

public static async Task<string> GetTokenAsync()
{
try
{
using (var httpClient = new HttpClient())
{
httpClient.DefaultRequestHeaders.Add("Ocp-Apim-Subscription-Key", SUB_KEY);
var builder = new UriBuilder(TOKEN_URI);
var result = await httpClient.PostAsync(builder.Uri.AbsoluteUri, null);
return await result.Content.ReadAsStringAsync();
}

}
catch (Exception ex)
{
logger.Error(ex, ex.Message);
return "出错了";
}
}

public static async Task RequestSSML(string authToken, string text, string fileName)
{
try
{
using (var httpClient = new HttpClient())
{
var body = "<speak xmlns=\"http://www.w3.org/2001/10/synthesis\" xmlns:mstts=\"http://www.w3.org/2001/mstts\" version=\"1.0\" xml:lang=\"zh-CN\"><voice name=\"xixi\">" + text + "</voice></speak>";
var request = new HttpRequestMessage()
{
Method = HttpMethod.Post,
RequestUri = new Uri(HOST),
Content = new StringContent(body, Encoding.UTF8, "application/ssml+xml")
};
request.Headers.Add("Authorization", "Bearer " + authToken);
request.Headers.Add("Connection", "Keep-Alive");
request.Headers.Add("User-Agent", RESOURCE_NAME);
request.Headers.Add("X-Microsoft-OutputFormat", "riff-24khz-16bit-mono-pcm");

var response = await httpClient.SendAsync(request);
if (response.StatusCode != System.Net.HttpStatusCode.OK)
{
Console.WriteLine("The Response {0}", response.StatusCode);
return;
}
using (var stream = await response.Content.ReadAsStreamAsync())
{
stream.Position = 0;
using (var fs = new FileStream(fileName, FileMode.Create, FileAccess.Write, FileShare.ReadWrite))
{
await stream.CopyToAsync(fs);
fs.Close();
}
}
}
}
catch (Exception ex)
{
logger.Error(ex, ex.Message);
}
}
}

调用这两个API:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
[HttpPost]
public IActionResult Voices(Context context)
{
logger.LogError(520, $"问:{ context.body}");
var text = TulingHelper.RequestTuling(context.body).ConfigureAwait(false).GetAwaiter().GetResult();
logger.LogError(521, $"答:{text}");

var result = VoicesHelper.GetTokenAsync().ConfigureAwait(false).GetAwaiter();
string token = result.GetResult();

string fileName = $"/voiceswav/{Guid.NewGuid().ToString()}.wav";
var task1 = VoicesHelper.RequestSSML(token, text, $"./wwwroot{fileName}");
task1.ConfigureAwait(false).GetAwaiter().GetResult();
return Ok(fileName);
}

我使用Angular 7,我希望有一个按钮,可以将我的应用程序全屏显示。 我使用HTML5 Fullscreen API,我有两个功能:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
openfullscreen() {
// Trigger fullscreen
console.log('gg');
if (document.documentElement.requestFullscreen) {
document.documentElement.requestFullscreen();
} else if (document.documentElement.mozRequestFullScreen) { /* Firefox */
document.documentElement.mozRequestFullScreen();
} else if (document.documentElement.webkitRequestFullscreen) { /* Chrome, Safari and Opera */
document.documentElement.webkitRequestFullscreen();
} else if (document.documentElement.msRequestFullscreen) { /* IE/Edge */
document.documentElement.msRequestFullscreen();
}
this.isfullscreen = true;
}

closefullscreen(){
if (document.exitFullscreen) {
document.exitFullscreen();
} else if (document.mozCancelFullScreen) { /* Firefox */
document.mozCancelFullScreen();
} else if (document.webkitExitFullscreen) { /* Chrome, Safari and Opera */
document.webkitExitFullscreen();
} else if (document.msExitFullscreen) { /* IE/Edge */
document.msExitFullscreen();
}
this.isfullscreen = false;
}

它能工作,但会报很多错误警告:

1
2
3
4
5
6
7
8
9
10
11
12
error TS2339: Property 'mozRequestFullScreen' does not exist on type 'HTMLElement'.
error TS2339: Property 'mozRequestFullScreen' does not exist on type 'HTMLElement'.
error TS2339: Property 'webkitRequestFullscreen' does not exist on type 'HTMLElement'.
error TS2339: Property 'webkitRequestFullscreen' does not exist on type 'HTMLElement'.
error TS2551: Property 'msRequestFullscreen' does not exist on type 'HTMLElement'. Did you mean 'requestFullscreen'?
error TS2551: Property 'msRequestFullscreen' does not exist on type 'HTMLElement'. Did you mean 'requestFullscreen'?
error TS2339: Property 'mozCancelFullScreen' does not exist on type 'Document'.
error TS2339: Property 'mozCancelFullScreen' does not exist on type 'Document'.
error TS2339: Property 'webkitExitFullscreen' does not exist on type 'Document'.
error TS2339: Property 'webkitExitFullscreen' does not exist on type 'Document'.
error TS2551: Property 'msExitFullscreen' does not exist on type 'Document'. Did you mean 'exitFullscreen'?
error TS2551: Property 'msExitFullscreen' does not exist on type 'Document'. Did you mean 'exitFullscreen'?

改进方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
openfullscreen() {
// Trigger fullscreen
const docElmWithBrowsersFullScreenFunctions = document.documentElement as HTMLElement & {
mozRequestFullScreen(): Promise<void>;
webkitRequestFullscreen(): Promise<void>;
msRequestFullscreen(): Promise<void>;
};

if (docElmWithBrowsersFullScreenFunctions.requestFullscreen) {
docElmWithBrowsersFullScreenFunctions.requestFullscreen();
} else if (docElmWithBrowsersFullScreenFunctions.mozRequestFullScreen) { /* Firefox */
docElmWithBrowsersFullScreenFunctions.mozRequestFullScreen();
} else if (docElmWithBrowsersFullScreenFunctions.webkitRequestFullscreen) { /* Chrome, Safari and Opera */
docElmWithBrowsersFullScreenFunctions.webkitRequestFullscreen();
} else if (docElmWithBrowsersFullScreenFunctions.msRequestFullscreen) { /* IE/Edge */
docElmWithBrowsersFullScreenFunctions.msRequestFullscreen();
}
this.isfullscreen = true;
}

closefullscreen(){
const docWithBrowsersExitFunctions = document as Document & {
mozCancelFullScreen(): Promise<void>;
webkitExitFullscreen(): Promise<void>;
msExitFullscreen(): Promise<void>;
};
if (docWithBrowsersExitFunctions.exitFullscreen) {
docWithBrowsersExitFunctions.exitFullscreen();
} else if (docWithBrowsersExitFunctions.mozCancelFullScreen) { /* Firefox */
docWithBrowsersExitFunctions.mozCancelFullScreen();
} else if (docWithBrowsersExitFunctions.webkitExitFullscreen) { /* Chrome, Safari and Opera */
docWithBrowsersExitFunctions.webkitExitFullscreen();
} else if (docWithBrowsersExitFunctions.msExitFullscreen) { /* IE/Edge */
docWithBrowsersExitFunctions.msExitFullscreen();
}
this.isfullscreen = false;
}

翻译自:https://stackoverflow.com/questions/54242775/angular-7-how-does-work-the-html5-fullscreen-api-ive-a-lot-of-errors