0%

URL

一般来说,URL只能使用英文字母、阿拉伯数字和某些标点符号,不能使用其他文字和符号。

只有字母和数字[0-9a-zA-Z]、一些特殊符号”$-_.+!*’(),”[不包括双引号]、以及某些保留字,才可以不经过编码直接用于URL。

URL编码

网址路径

http://zh.wikipedia.org/wiki/春节的实际地址是https://zh.wikipedia.org/wiki/%E6%98%A5%E8%8A%82

“春”和”节”的utf-8编码分别是”E6 98 A5”和”E8 8A 82”,因此,”%E6%98%A5%E8%8A%82”就是按照顺序,在每个字节前加上%而得到的。

网址路径的编码,用的是utf-8编码

查询字符串

http://www.baidu.com/s?wd=春节,各浏览器编码不一致,用的是操作系统的默认编码。

GET方法生成的URL

在已打开的网页上,直接用Get或Post方法发出HTTP请求。

GET和POST方法的编码,用的是网页的编码

Ajax调用的URL

在Ajax调用中,IE总是采用GB2312编码(操作系统的默认编码),而Firefox总是采用utf-8编码。

escape()

现在已经不再使用了。

除了ASCII字母、数字、标点符号”@ * _ + - . /“以外,对其他所有字符进行编码。返回一个字符的Unicode编码值,在\u0000到\u00ff之间的符号被转成%xx的形式,其余符号被转成%uxxxx的形式。对应的解码函数是unescape()。

escape()不对”+”编码。网页在提交表单的时候,如果有空格,则会被转化为+字符。服务器处理数据的时候,会把+号处理成空格。

encodeURI()

它着眼于对整个URL进行编码,因此除了常见的符号以外,对其他一些在网址中有特殊含义的符号”; / ? : @ & = + $ , #”,也不进行编码。编码后,它输出符号的utf-8形式,并且在每个字节前加上%。

解码函数是decodeURI()。

不对单引号’编码。

encodeURIComponent()

用于对URL的组成部分进行个别编码,而不用于对整个URL进行编码。因此,”; / ? : @ & = + $ , #”,这些在encodeURI()中不被编码的符号,在encodeURIComponent()中统统会被编码。至于具体的编码方法,两者是一样。

解码函数是decodeURIComponent()。

基础使用

当复用一个组件时,大部分的内容是相同的,只有一部分的内容是不同的,这时可以使用ng-content指令来提高组件的复用性。
内容投影,即通过使用指令来实现内容投影的功能。

以下User的定义如下:

1
2
3
4
5
User定义内容:
export interface User {
email: string;
password: string;
}

定义一个ChildComponent组件:

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
import { Component, Output, EventEmitter } from "@angular/core";

import { User } from "./auth-form.interface";

@Component({
selector: "app-child",
template: `
<div>
<form (ngSubmit)="onSubmit(form.value)" #form="ngForm">
<ng-content></ng-content>
<label>
邮箱
<input type="email" name="email" ngModel>
</label>
<label>
密码
<input type="password" name="password" ngModel>
</label>
<button type="submit">
提交
</button>
</form>
</div>
`
})
export class ChildComponent {
@Output() submitted: EventEmitter<User> = new EventEmitter<User>();

onSubmit(value: User) {
this.submitted.emit(value);
}
}

在ParentComponent组件中,使用已经定义的ChildComponent组件:

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
import { Component } from "@angular/core";

import { User } from "./auth-form/auth-form.interface";

@Component({
selector: "app-parent",
template: `
<div>
<app-child
(submitted)="createUser($event)">
<h3>注册</h3>
<button type="submit">
注册
</button>
</app-child>
<app-child
(submitted)="loginUser($event)">
<h3>登录</h3>
<button type="submit">
登录
</button>
</app-child>
</div>
`
})
export class ParentComponent {
createUser(user: User) {
console.log("Create account", user);
}

loginUser(user: User) {
console.log("Login", user);
}
}

在app-parent中app-child标签内的多余的html会被投影到ChildComponent组件的’ng-content’中。

select

select属性支持CSS选择器(element,class,attribute[name=xxx])来匹配所需要的内容。如果ng-content上没有设置select属性,他将接收全部内容,或接收不匹配的任何其他的ng-content元素的内容。如果有多个相同element,class等都会被ng-content投影。

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
<div>
<form #form="ngForm" (ngSubmit)="onSubmit(form.value)" novalidate>
<ng-content select="h3"></ng-content>
<label>
邮箱
<input type="email" name="email" ngModel>
</label>
<label>
密码
<input type="password" name="password" ngModel>
</label>
<ng-content select="app-auth-remember"></ng-content>
<ng-content select="button"></ng-content>
</form>
</div>


// app-auth-remember组件内容

import {Component, OnInit, Output, EventEmitter} from '@angular/core';
@Component({
selector: 'app-auth-remember',
template: `
<label>
<input type="checkbox" (change)="onChecked($event.target.checked)">
Keep me logged in
</label>
`,
styleUrls: ['./auth-remember.component.css']
})
export class AuthRememberComponent implements OnInit {
@Output() checked: EventEmitter<boolean> = new EventEmitter<boolean>();

constructor() {
}

ngOnInit() {
console.log('初始化已完成!');
}

onChecked(value: boolean) {
this.checked.emit(value);
}
}

app-child的组件中ng-content的select属性分别对应h3和button,也就是分别映射h3标签和button标签。如果有多个ng-content标签都没有select属性,则默认最后一个ng-content匹配所有的标签。

warning:如果想要正确根据select属性投射内容,限制就是这些标签必须是组件标签的直接子节点。
如果不是直接子节点,将不会被匹配到,如果要解决这个问题就必须在需要被匹配到的标签上,加上属性ngProjectAs="xxx",就可以通过select="xxx"进行投影。

组件投影

组件必须在带有ng-content的组件标签内,这样ng-content才能匹配到组件。当然这样操作会对性能有一定的影响,因为ng-content不会产生内容,他只是投影现有的内容,因此这些组件一开始已经被制造,投影内容的生命周期将被绑定到他被声明的地方,而不是在显示的地方。

child-component

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import { Component, OnInit } from '@angular/core';

@Component({
selector: 'demo-child-component',
template: '<h3>我是child-component组件</h3>'
})
export class ChildComponent implements OnInit {

constructor() {
}

ngOnInit() {
console.log('child-component初始化完成!');
}
}

parent-component

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import { Component, OnInit } from '@angular/core';

@Component({
selector: 'parent-component',
template: `
<button (click)="show = !show">
{{ show ? 'Hide' : 'Show' }}
</button>
<div class="content" *ngIf="show">
<ng-content></ng-content>
</div>
`
})
export class ParentComponent implements OnInit {
show = true;

constructor() {
}

ngOnInit() {
}
}

然后将child-component投射到parent-component中

1
2
3
<parent-component>
<child-component></child-component>
</parent-component>

在控制台只能看到一次’child-component初始化完成!’,点击按钮切换,不在打印,说明该组件只被实例化了一次——未被销毁和重新创建。当然可以在parent-component标签中对要投影的组件进行控制。

ContentChild

获取ng-content投射组件的内容

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
@Component({
selector: "app-child",
template: `
<div>
<form (ngSubmit)="onSubmit(form.value)" #form="ngForm">
<ng-content select="h3"></ng-content>
<label>
Email address
<input type="email" name="email" ngModel>
</label>
<label>
Password
<input type="password" name="password" ngModel>
</label>
<ng-content select="app-auth-remember"></ng-content>
<div *ngIf="showMessage">
保持登录状态30天
</div>
<ng-content select="button"></ng-content>
</form>
</div>
`
})
export class AuthFormComponent implements AfterContentInit {
showMessage: boolean;

@ContentChild(AuthRememberComponent) remember: AuthRememberComponent;

ngAfterContentInit() {
if (this.remember) {
// 因为在app-auth-remember组件已经将值checked进行EventEmitter处理且进行了emit,故而这里可以动态的获取值。
this.remember.checked.subscribe(
(checked: boolean) => (this.showMessage = checked)
);
}
}
// ...
}

通过 ContentChild(AuthRememberComponent) 来设置获取的组件类型,此外我们在生命周期钩子 ngAfterContentInit 中通过订阅 remember 的 checked 输出属性来监听 checkbox 输入框的变化。

ContentChildren

与ContentChild类似通过Content Projection方式设置的视图中获取匹配的多个元素,返回的结果是一个QueryList集合。

在parent-component中添加多个app-auth-remember:

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
@Component({
selector: "app-parent",
template: `
<div>
<app-child
(submitted)="createUser($event)">
<h3>注册</h3>
<button type="submit">
注册
</button>
</app-child>
<app-child
(submitted)="loginUser($event)">
<h3>登录</h3>
<app-auth-remember (checked)="rememberUser($event)"></app-auth-remember>
<app-auth-remember (checked)="rememberUser($event)"></app-auth-remember>
<app-auth-remember (checked)="rememberUser($event)"></app-auth-remember>
<button type="submit">
登录
</button>
</app-child>
</div>
`
})
export class AppComponent {
// ...
}

然后在ChildComponent中引入ContentChildren装饰器

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
import { Component, Output, EventEmitter, ContentChildren, QueryList, AfterContentInit } from '@angular/core';
import { AuthRememberComponent } from './auth-remember.component';
import { User } from './auth-form.interface';

@Component({
selector: 'app-child',
template: `
<div>
<form (ngSubmit)="onSubmit(form.value)" #form="ngForm">
<ng-content select="h3"></ng-content>
<label>
邮箱
<input type="email" name="email" ngModel>
</label>
<label>
密码
<input type="password" name="password" ngModel>
</label>
<ng-content select="app-auth-remember"></ng-content>
<div *ngIf="showMessage">
保持登录30天
</div>
<ng-content select="button"></ng-content>
</form>
</div>
`
})
export class AuthFormComponent implements AfterContentInit {

showMessage: boolean;

@ContentChildren(AuthRememberComponent) remember: QueryList<AuthRememberComponent>;

@Output() submitted: EventEmitter<User> = new EventEmitter<User>();

ngAfterContentInit() {
if (this.remember) {
this.remember.forEach((item) => {
item.checked.subscribe((checked: boolean) => this.showMessage = checked);
});
}
}

// ...
}

ContentChildren 装饰器返回的是一个 QueryList 集合,在 ngAfterContentInit 生命周期钩子中,我们通过 QueryList 实例提供的 forEach 方法来遍历集合中的元素。QueryList 实例除了提供 forEach() 方法之外,它还提供了数组常用的方法,比如 map()、filter()、find()、some() 和 reduce() 等方法。

GET

设置查询参数

1
https://www.juphy.cn/todos?_page=1&_limit=10

创建HttpParams对象

  1. 直接链式创建

    1
    2
    3
    4
    5
    import { HttpClient, HttpParams } from '@angular/common/http';

    const params = new HttpParams().set('_page', 1).set('_limit', 10);

    this.http.get('https://www.juphy.cn/todos', {params});

    通过链式语法调用 set() 方法,构建 HttpParams 对象。这是因为HttpParams对象是不可变的,通过set() 方法可以防止该对象被修改。每当调用 set() 方法,将会返回包含新值的 HttpParams 对象。

  2. 使用formString

1
const params = new HttpParams({formString: '_page=1&_limit=10'});
  1. 使用formObject
1
const params = new HttpParams({ fromObject: { _page: "1", _limit: "10" } });
  1. 使用request API
1
this.http.request('GET', 'https://www.juphy.cn/todos', {params});

获取完整响应

默认情况下,httpClient服务返回的是响应体,如果需要获取响应头的相关信息,可以设置options的对象的observe属性值为response来获取完整的响应对象。

1
2
3
4
5
this.http.get('https://www.juphy.cn/todos',{
observe: 'response'
}).subscribe(res => {
console.dir('Response:'+res.status);
})

设置响应类型

options设置responseType属性的值,’text’,’arraybuffer’,’blob’。

设置Http Headers

1
2
3
const params = new HttpParams({ fromObject: { _page: "1", _limit: "10" } });
const headers = new HttpHeaders().set('Content-Type', 'application/json; charset=UTF-8');
this.http.get('url', {headers, params});

多个http请求

并行发送

1
2
3
4
5
6
7
const parallel$ = forkJoin(
this.http.get('url1'),
this.http.get('url2')
);
parallel$.subscribe(res =>{
res是一个数组包含多个请求返回的结果。
})

顺序发送

1
2
3
4
5
6
7
8
9
10
const sequence$ = this.http.get('url1')
.pipe(
switchMap(res =>{
// 此res是url1返回的结果
return this.http.get('url2');
})
);
sequence$.subscribe(res=>{
res// 此res是url2返回的结果
})

控制返回结果

1
2
3
4
5
6
7
8
9
10
const sequence$ = this.http.get('url1')
.pipe(
switchMap(res =>{
// 此res是url1返回的结果
return this.http.get('url2');
}, (res1, res2) => [res1, res2]) // 此处控制返回结果
);
sequence$.subscribe(res=>{
res; // [res1, res2]
})

请求异常处理

1
2
3
4
5
6
7
8
9
import { of } from "rxjs";
import { catchError } from "rxjs/operators";

this.http.get('url').pipe(
catchError(error => {
console.error('Error catched', error);
return of({description: 'Error Value Emitted'})
})
).subscribe(res => console.log(res));

Http拦截器

定义拦截器

拦截器提供了一种用于拦截、修改请求和响应的机制,类似于express中间件。

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
import { Injectable } from "@angular/core";
import { HttpEvent, HttpRequest, HttpHandler, HttpInterceptor } from "@angular/common/http";
import { Observable, of, throwError } from 'rxjs';
import { mergeMap, catchError } from 'rxjs/operators';

@Injectable()
// 定义一个类并实现HttpInterceptor接口
export class DefaultInterceptor implements HttpInterceptor {
constructor(private injector: Injector){}

private handleData(
event: HttpResponse<any> | HttpErrorResponse
): Observable<any>{
console.log(event);
switch(event.status){
case 200:
break;
case 401:
break;
case 403:
break;
case 404:
break;
case 500:
break;
default:
if(event instanceof HttpErrorResponse){
console.warn(
'未可知错误,大部分是由于后端不支持CORS或无效配置引起',
event
);
}
break;
}
return of(event);
}
intercept(
req: HttpRequest<any>, // HttpRequest,即请求对象
next: HttpHandler // HttpHandler,该对象有一个handle()方法,该方法返回一个Observable对象。
): Observable<HttpEvent<any>> {
let url= req.url; // 设置url形式,统一加上服务端前缀
const newReq = req.clone(
url: url,
headers: req.headers.set("X-CustomAuthHeader", "iloveangular")
);
return next.handle(newReq).pipe(
mergeMap((event: any) => {
// 允许统一对请求错误进行处理,这是因为一个请求若是业务上错误的情况下其HTTP请求的状态是200的情况下需要
if(event instanceof HttpReponse && event.status === 200) return this.handleData(event);
// 若一切正常,则后续操作
return of(event);
}),
catchError((err: HttpErrorResponse) => this.handleData(err))
);
}
}

CachingInterceptor

拦截器实现简单的缓存控制。

1
2
3
4
5
6
7
定义一个Cache接口:
import { HttpRequest, HttpResponse } from '@angular/common/http';

export interface Cache {
get(req: HttpRequest<any>): HttpResponse<any> | null;
put(req: HttpRequest<any>, res: HttpResponse<any>): void;
}
  • get(req: HttpRequest): HttpResponse | null —— 用于获取 req 请求对象对应的响应对象;
  • put(req: HttpRequest, res: HttpResponse): void; —— 用于保存 req 对象对应的响应对象。

在实际的场景中,一般会设置一个最大的缓存时间,即缓存的有效期,在有效期内,如果缓存存在,则会直接返回已缓存的响应对象。

1
2
3
4
5
6
7
8
9
import { HttpResponse } from "@angular/common/http";

export const MAX_CACHE_AGE = 30000; // 单位为毫秒

export interface CacheEntry {
url: string;
response: HttpResponse<any>;
entryTime: number;
}
  • url: string —— 被缓存的请求 URL 地址
  • response: HttpResponse —— 被缓存的响应对象
  • entryTime: number —— 响应对象被缓存的时间,用于判断缓存是否过期

实现CacheService

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
import { Injectable } from "@angular/core";
import { HttpRequest, HttpResponse } from "@angular/common/http";
import { Cache } from "./cache";
import { CacheEntry, MAX_CACHE_AGE } from "./cache.entry";

@Injectable({
providedIn: "root"
})
export class CacheService implements Cache {
cacheMap = new Map<string, CacheEntry>();

constructor() {}

get(req: HttpRequest<any>): HttpResponse<any> | null {
// 判断当前请求是否已被缓存,若未缓存则返回null
const entry = this.cacheMap.get(req.urlWithParams);
if (!entry) return null;
// 若缓存存在,则判断缓存是否过期,若已过期则返回null。否则返回请求对应的响应对象
const isExpired = Date.now() - entry.entryTime > MAX_CACHE_AGE;
console.log(`req.urlWithParams is Expired: ${isExpired} `);
return isExpired ? null : entry.response;
}

put(req: HttpRequest<any>, res: HttpResponse<any>): void {
// 创建CacheEntry对象
const entry: CacheEntry = {
url: req.urlWithParams,
response: res,
entryTime: Date.now()
};
console.log(`Save entry.url response into cache`);
// 以请求url作为键,CacheEntry对象为值,保存到cacheMap中。并执行
// 清理操作,即清理已过期的缓存。
this.cacheMap.set(req.urlWithParams, entry);
this.deleteExpiredCache();
}

private deleteExpiredCache() {
this.cacheMap.forEach(entry => {
if (Date.now() - entry.entryTime > MAX_CACHE_AGE) {
this.cacheMap.delete(entry.url);
}
});
}
}

实现CachingInterceptor

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
import { CacheService } from '../cache.service';
const CACHABLE_URL = 'url';

@Injectable()
export class CachingInterceptor implements HttpInterceptor {
constructor(private cache: CacheService) {}

intercept(req: HttpRequest<any>, next: HttpHandler) {
// 判断当前请求是否可缓存
if (!this.isRequestCachable(req)) {
return next.handle(req);
}
// 获取请求对应的缓存对象,若存在则直接返回该请求对象对应的缓存对象
const cachedResponse = this.cache.get(req);
if (cachedResponse !== null) {
return of(cachedResponse);
}
// 发送请求至API站点,请求成功后保存至缓存中
return next.handle(req).pipe(
tap(event => {
if (event instanceof HttpResponse) {
this.cache.put(req, event);
}
})
);
}

// 判断当前请求是否可缓存
private isRequestCachable(req: HttpRequest<any>) {
return (req.method === 'GET') && (req.url.indexOf(CACHABLE_URL) > -1);
}
}

应用拦截器

app.module.ts

1
2
3
4
5
6
7
8
9
10
import DefaultInterceptor
@NgModule({
declarations: [AppComponent],
imports: [BrowserModule, HttpClientModule],
providers: [
{ provide: HTTP_INTERCEPTORS, useClass: AuthInterceptor, multi: true }
],
bootstrap: [AppComponent]
})
export class AppModule {}

Http进度事件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
this.http.get('url', {
observe: 'events',
reportProgress: true
}).subscribe((event: HttpEvent<any>) => {
switch(event.type){
case HttpEventType.Sent:
console.log("Request sent!");
break;
case HttpEventType.ResponseHeader:
console.log("Response header received!");
break;
case HttpEventType.DownloadProgress:
const kbLoaded = Math.round(event.loaded / 1024);
console.log(`Download in progress! ${kbLoaded}Kb loaded`);
break;
case HttpEventType.Response:
console.log("Done!", event.body);
}
})

控制台输出:

1
2
3
4
5
Request sent!
Response header received!
Download in progress! 6Kb loaded
Download in progress! 24Kb loaded
Done!
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
this.http.get('url', {
observe: 'events',
reportProgress: true
}).subscribe((event: HttpEvent<any>) => {
switch(event.type){
case HttpEventType.Sent:
console.log("Request sent!");
break;
case HttpEventType.ResponseHeader:
console.log("Response header received!");
break;
case HttpEventType.DownloadProgress:
const kbLoaded = Math.round(event.loaded / 1024);
console.log(`Download in progress! ${kbLoaded}Kb loaded`);
break;
case HttpEventType.Response:
console.log("Done!", event.body);
}
})

控制台输出:

1
2
3
4
5
Request sent!
Response header received!
Download in progress! 6Kb loaded
Download in progress! 24Kb loaded
Done!

安装

使用npm

1
npm install xlsx -S

直接使用

1
<script lang="javascript" src="xlsx.full.min.js"></script>

老版本浏览器

1
2
3
4
<!-- add the shim first -->
<script type="text/javascript" src="shim.min.js"></script>
<!-- after the shim is referenced, add the library -->
<script type="text/javascript" src="xlsx.full.min.js"></script>

Angular 2+

1
import * as XLSX from 'xlsx';

sheet_to_json

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/* <input type="file" (change)="onFileChange($event)" multiple="false" /> */

onFileChange(evt: any) {
/* wire up file reader */
const target: DataTransfer = <DataTransfer>(evt.target);
if (target.files.length !== 1) throw new Error('Cannot use multiple files');
const reader: FileReader = new FileReader();
reader.onload = (e: any) => {
/* read workbook */
const bstr: string = e.target.result;
const wb: XLSX.WorkBook = XLSX.read(bstr, {type: 'binary'});

/* grab first sheet */
const wsname: string = wb.SheetNames[0];
const ws: XLSX.WorkSheet = wb.Sheets[wsname];

/* save data */
this.data = <AOA>(XLSX.utils.sheet_to_json(ws, {header: 1}));
};
reader.readAsBinaryString(target.files[0]);
}

结合Ant Design使用

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
/*
<nz-upload [nzBeforeUpload]="beforeUpload">
<button nz-button>导入文件</button>
</nz-upload>
*/

reader: FileReader = new FileReader();

constructor(){
this.reader.onloadstart=()=>{

}

this.reader.onload = (e: any)=>{
const bstr: string = e.target.result;
const wb: XLSX.WorkBook = XLSX.read(bstr, {type: 'binary'});
const sheet = wb.Sheets[wb.SheetNames[0]];
const json = XLSX.utils.sheet_to_json(sheet);
}

this.header.onloadend = () =>{

}
}

beforeUpload = (file: UploadFile): boolean => {
this.reader.readAsBinaryString(this.file);
return false;
}

json_to_sheet/aoa_to_sheet

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
/* generate worksheet */
const ws: XLSX.WorkSheet = XLSX.utils.json_to_sheet(data);
const ws: XLSX.WorkSheet = XLSX.utils.aoa_to_sheet(data);

/* generate workbook and add the worksheet */
const wb: XLSX.WorkBook = XLSX.utils.book_new();
XLSX.utils.book_append_sheet(wb, ws, 'Sheet1');

/* save to file */
XLSX.writeFile(wb, 'SheetJS.xlsx');

或者使用file-saver中的saveAs

import {saveAs} from 'file-save

const wbout: string = XLSX.write(wb, {bookType: 'xlsx', bookSST: true, type: 'binary'});

const s2ab = (s: any) => {
const buf = new ArrayBuffer(s.length);
const view = new Uint8Array(buf);
for (let i = 0; i !== s.length; ++i) view[i] = s.charCodeAt(i) & 0xFF;
return buf;
};

saveAs(new Blob([s2ab(wbout)], {type: 'application/octet-stream'}), 'sheetJS.xlsx');

XMLHttpRequest and fetch

GET

1
2
3
4
5
6
7
8
9
let xhr = XMLHttpRequest();
req.open('GET', 'sheetjs.xlsx', true);
req.onload = function(e){
此时req.response是一个ArrayBuffer,通过new Uint8Array()创建不带符号整数的Uint8Array构造函数。
let data = new Uint8Array(req.response);
let wb = XLSX.read(data, {type: "array"});
document.getElementById('xxx').innerHTML = XLSX.units.sheet_to_html(wb.Sheets[wb.SheetNames[0]],{editable: true}).replace("<table",'<table id="table" border="1"');
}
req.send();

POST

1
2
3
4
5
6
let wb = XLSX.utils.table_to_book(document.getElementById('xxx'));
let fd = new FormData();
let data = XLSX.write(wb, {bookType: '文件格式', type: 'array'});
fd.append('data', new File([data], '文件名'+'.'+'文件格式(csv, xlsx, xls等)'));
req.open('POST', url, true);
req.send(fd);

在angular2+中使用

1
2
3
this.http.get('sheetjs.xlsx', {responseType: 'arraybuffer'}).subscribe(res =>{
console.log(res); // res是ArrayBuffer
})
1
2
3
4
5
6
7
let wb = XLSX.utils.table_to_book(document.getElementById('xxx'));
let fd = new FormData();
let data = XLSX.write(wb, {bookType: '文件格式', type: 'array'});
fd.append('data', new File([data], '文件名.格式名'));
this.http.post('url', fd, {'responseType': 'blob'或者'arraybuffer'}).subscribe(res =>{
console.log(res);
})

nodejs后端:

1
2
3
4
5
6
7
8
app.post('/upload', (req, res) =>{
res.header('Access-Control-Allow-Origin', '*');
let f = req.files[Object.keys(req.files)[0]];
// f.name 生成的excel的名字和格式
let newpath = path.join(__dirname, f.name); // 定义新文件所在的位置
fs.renameSync(f.path, newpath);
re.end('xx');
})

其他使用功能

修改某一单元的数据,合并单元格问题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
const ws: any = XLSX.utils.json_to_sheet(data);
// 修改填充的数据
ws['A1']={t: 's', v: '需要填写的内容'};
ws['A2']={t: 's', v: '需要填写的内容'};
ws['A3']={t: 's', v: '需要填写的内容'};

// 合并单元格
/*
c: 代表纵向,从0开始
r: 代表横向,从0开始
*/
ws['!merges']=[
{
s: {c: 0, r: 0},
e: {c: 10, r: 0}
},
{
s: {c: 1, r: 0},
e: {c: 1, r: 10}
}
]

单元格

Key Description
v 原始值
w 格式化文本
t type: b Boolean, e Error, n Number, d Date, s Text, z Stub

多种导出形式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
> Importing:
aoa_to_sheet converts an array of arrays of JS data to a worksheet.
json_to_sheet converts an array of JS objects to a worksheet.
table_to_sheet converts a DOM TABLE element to a worksheet.
sheet_add_aoa adds an array of arrays of JS data to an existing worksheet.
sheet_add_json adds an array of JS objects to an existing worksheet.

> Exporting:
sheet_to_json converts a worksheet object to an array of JSON objects.
sheet_to_csv generates delimiter-separated-values output.
sheet_to_txt generates UTF16 formatted text.
sheet_to_html generates HTML output.
sheet_to_formulae generates a list of the formulae (with value fallbacks).

> Cell and cell address manipulation:
format_cell generates the text value for a cell (using number formats).
encode_row / decode_row converts between 0-indexed rows and 1-indexed rows.
encode_col / decode_col converts between 0-indexed columns and column names.
encode_cell / decode_cell converts cell addresses.
encode_range / decode_range converts cell ranges.

详细使用SheetJS

内置管道

大小写转换

1
2
<p>{{'Angular' | uppercase}}</p> // ANGULAR
<p>{{'Angular' | lowercase}}</p> // angular

数值格式化

1
2
3
4
5
6
pi: number = 3.14
e: number = 2.718281828
<p>{{e | number: '3.1-5'}}</p> // 002.71828
<p>{{e | number: '4.5-5'}}</p> // 0,002.71828
<p>{{e | number: '4.0'}}</p> // 0,002.718
<p>{{e | number: '4.'}}</p> // 0,002.718

{minIntegerDigits}.{minFractionDigits}-{maxfractionDigits}

  • minIntegerDigits:整数部分保留最小的位数,默认值为1.
  • minFractionDigits:小数部分保留最小的位数,默认值为0.
  • maxFractionDigits:小数部分保留最大的位数,默认值为3.
    小数点左边的数字表示最少保留的位数,如果原数值整数位不足,则用0补齐;小数点右边表示小数的最小位数和小数的最大位数,如果原值的小数位大于最大位数,要四舍五入保留到最大位数;如果原值的小数位小于最小位数,则要不足小数位补0。

    日期格式化

日期 标志符 缩写 全称 单标志符 双标志符
地区 G G(AD) GGGG(Anno Domini)
y y(2016) yy(16)
M MMM(Jun) MMMM(June) M(6) MM(06)
d d(8) dd(08)
星期 E E,EE,EEE(Fri) EEEE(Friday)
时间(AM,PM) j j(8 PM) jj(08 PM)
12小时制 h h(8) hh(08)
24小时制 H H(20) HH(20)
m m(5) mm(05)
s s(8) ss(08)
时区 Z Z(china Standard Time)
时区 z z(GMT-8:00)
1
2
date:Date = new Date('2016-06-08 20:05:08');
<p>{{date | date: "y-MM-dd EEEE"}}</p> //2016-06-08 Wednesday

PercentPipe

1
2
3
a = 0.269
<p>{{a | percent}}</p> // 27%
<p>{{a | percent:'4.3-5'}}</p> // 0,026.900%

{ value_expression | percent [:digitsInfo[:locale]] }
如果没有digitsInfo则按照整数取(四舍五入),如果有digitsInfo,则按照规则取。

CurrencyPipe

expression | currency[: currencyCode[: display[: digitInfo]]]

  • currency 要显示内容(如’USD’,也可以是自定义的)
  • currencyCode
    • ‘code’ 显示内容(如’USD’)
    • ‘symbol’(默认) 显示符号(例如$)
    • ‘symbol-narrow’
    • 布尔值 true用于符号 false用于code
  • digitInfo 按照数值的规则

SlicePipe(非纯管道)

1
2
3
4
str = 'abcdefghij';
<p>{{str | slice:0:4}}</p> // 'abcd'
<p>{{str | slice:4:0}}</p> // ''
<p>{{str | slice:-4}}</p> // 'ghij'

JsonPipe(非纯管道)

将数据对象通过JSON.stringify()转换成对象字符串,并不改变原数据。

管道分类

纯管道

仅当管道输入值变化的时候,才执行转换操作,默认的类型是 pure 类型。(备注:输入值变化是指原始数据类型如:string、number、boolean、Symbol等的数值发生变化或者对对象引用(Date、Array、Function、Object)的更改)。Angular会忽略对象内部的更改,除非是引用地址的变化。

纯管道使用纯函数, 纯函数是指在处理输入并返回结果时,不会产生任何副作用的函数。 给定相同的输入,它们总是返回相同的输出。

非纯管道

angular组件在每次变化检测期间都会执行,如鼠标点击或移动都会执行 impure 管道。非纯管道可能会被调用很多次,因此必须小心使用非纯管道。

将管道设置为非纯管道:

1
2
3
4
@Pipe({
name: 'SexReformPipe',
pure: false
})

管道链

将多个管道用|连接在一起,组成管道链对数据进行处理。

1
<p>{{'abcdefgh' | slice:0:3 | uppercase}}</p>  // 'ABC'

自定义管道

  • @Pipe装饰器定义Pipe的metadata信息
  • 实现PipeTransform接口中定义的transform方法,接受一个输入值和一些可选参数,并返回转换后的值
  • 不可以返回html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import { Pipe, PipeTransform } form '@angular/core'
@Pipe({name: 'exponentialStrength'})
export class ExponentialStrengthPipe implements PipeTransform {
transform(value: number, exponent: string): number {
let exp = parseFloat(exponent);
return Math.pow(value, isNaN(exp) ? 1 : exp);
}
}

@Pipe({ name: 'sexReform' })
export class SexReformPipe implements PipeTransform {
transform(value: string):string{
switch(value){
case 'male': return '男';
case 'female': return '女';
default: return '';
}
}
}

使用自定义管道

1
2
3
<p>Super power boost: {{2 | exponentialStrength: 10}}</p>
<p>{{sexValue | sexReform}}</p>
<p class="{{sexValue | sexReform}}"></p>

定义完管道之后,要手动注册自定义管道,添加到declarations数组中

非纯AsyncPipe

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
import { Component } from '@angular/core';

import { Observable, interval } from 'rxjs';
import { map, take } from 'rxjs/operators';

@Component({
selector: 'app-hero-message',
template: `
<h2>Async Hero Message and AsyncPipe</h2>
<p>Message: {{ message$ | async }}</p>
<button (click)="resend()">Resend</button>`,
})
export class HeroAsyncMessageComponent {
message$: Observable<string>;

private messages = [
'You are my hero!',
'You are the best hero!',
'Will you be my hero?'
];

constructor() { this.resend(); }

resend() {
this.message$ = interval(500).pipe(
map(i => this.messages[i]),
take(this.messages.length)
);
}
}

AsyncPipe接受一个Promise或Observable作为输入,并且自动订阅这个输入,最终返回他们给出的值。AsyncPipe 管道是有状态的。 该管道维护着一个所输入的 Observable 的订阅,并且持续从那个 Observable 中发出新到的值。

非纯且带缓存的管道

一个向服务器发起http请求的非纯管道。此管道只有在所请求的URL发生变化时才会向服务器发起请求,他会缓存服务器的响应。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import { Pipe, PipeTransform } from '@angular/core';
import { HttpClient } from '@angular/common/http';
@Pipe({
name: 'fetch',
pure: false
})
export class FetchJsonPipe implements PipeTransform {
private cachedData: any = null;
private cachedUrl = '';

constructor(private http: HttpClient) { }

transform(url: string): any {
if (url !== this.cachedUrl) {
this.cachedData = null;
this.cachedUrl = url;
this.http.get(url).subscribe( result => this.cachedData = result );
}

return this.cachedData;
}
}

在一个组件中使用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import { Component } from '@angular/core';

@Component({
selector: 'app-hero-list',
template: `
<h2>Heroes from JSON File</h2>

<div *ngFor="let hero of ('assets/heroes.json' | fetch) ">
{{hero.name}}
</div>

<p>Heroes as JSON:
{{'assets/heroes.json' | fetch | json}}
</p>`
})
export class HeroListComponent { }

其中assets/heroes.json的数据是:

1
2
3
4
5
6
[
{"name": "Windstorm", "canFly": true},
{"name": "Bombasto", "canFly": false},
{"name": "Magneto", "canFly": false},
{"name": "Tornado", "canFly": true}
]

最终结果如下:

1
2
3
4
5
6
7
8
Heros from JSON File

Windstorm
Bombasto
Magneto
Tornado

Heroes as JSON: [ { "name": "Windstorm", "canFly": true }, { "name": "Bombasto", "canFly": false }, { "name": "Magneto", "canFly": false }, { "name": "Tornado", "canFly": true } ]

显式转换

通过手动进行类型转换,Javascript提供了以下转型函数:

  • 转换为数值类型:Number(mix)、parseInt(string,radix)、parseFloat(string)
  • 转换为字符串类型:toString(radix)、String(mix)
  • 转换为布尔类型:Boolean(mix)

1、Number(mix)函数,可以将任意类型的参数mix转换为数值类型。其规则为:

  • (1)如果是布尔值,true和false分别被转换为1和0
  • (2)如果是数字值,返回本身。
  • (3)如果是null,返回0.
  • (4)如果是undefined,返回NaN。
  • (5)如果是字符串,遵循以下规则:
    • 1、如果字符串中只包含数字,则将其转换为十进制(忽略前导0)
    • 2、如果字符串中包含有效的浮点格式,将其转换为浮点数值(忽略前导0)
    • 3、如果是空字符串,将其转换为0
    • 4、如果字符串中包含非以上格式,则将其转换为NaN
  • (6)如果是对象,则调用对象的valueOf()方法,然后依据前面的规则转换返回的值。如果转换的结果是NaN,则调用对象的toString()方法,再次依照前面的规则转换返回的字符串值。

下表列出了对象的valueOf()的返回值:

对象 返回值
Array 数组的元素被转换为字符串,这些字符串由逗号分隔,连接在一起。其操作与 Array.toString 和 Array.join 方法相同。
Boolean Boolean 值。
Date 存储的时间是从 1970 年 1 月 1 日午夜开始计的毫秒数 UTC。
Function 函数本身。
Number 数字值。
Object 对象本身。这是默认情况。 “[object Object]”
String 字符串值。

2、parseInt(string, radix)函数,将字符串转换为整数类型的数值。它也有一定的规则:

  • (1)忽略字符串前面的空格,直至找到第一个非空字符
  • (2)如果第一个字符不是数字符号或者负号,返回NaN
  • (3)如果第一个字符是数字,则继续解析直至字符串解析完毕或者遇到一个非数字符号为止
  • (4)如果上步解析的结果以0开头,则将其当作八进制来解析;如果以0x开头,则将其当作十六进制来解析
  • (5)如果指定radix参数,则以radix为基数进行解析

3、parseFloat(string)函数,将字符串转换为浮点数类型的数值。

它的规则与parseInt基本相同,但也有点区别:字符串中第一个小数点符号是有效的,另外parseFloat会忽略所有前导0,如果字符串包含一个可解析为整数的数,则返回整数值而不是浮点数值。

4、toString(radix)方法。除undefined和null之外的所有类型的值都具有toString()方法,其作用是返回对象的字符串表示。

对象 操作
Array 将 Array 的元素转换为字符串。结果字符串由逗号分隔,且连接起来。
Boolean 如果 Boolean 值是 true,则返回 “true”。否则,返回 “false”。
Date 返回日期的文字表示法。
Error 返回一个包含相关错误信息的字符串。
Function 返回如下格式的字符串,其中 functionname 是被调用 toString 方法函数的名称:function functionname( ) { [native code] }
Number 返回数字的文字表示。
String 返回 String 对象的值。
默认 返回 “[object objectname]”,其中 objectname 是对象类型的名称。

5、String(mix)函数,将任何类型的值转换为字符串,其规则为:

  • (1)如果有toString()方法,则调用该方法(不传递radix参数)并返回结果
  • (2)如果是null,返回”null”
  • (3)如果是undefined,返回”undefined”

6、Boolean(mix)函数,将任何类型的值转换为布尔值。

以下值会被转换为false:false、””、0、NaN、null、undefined,其余任何值都会被转换为true。

隐式转换

在某些情况下,即使我们不提供显示转换,Javascript也会进行自动类型转换,主要情况有:

1、于检测是否为非数值的函数:isNaN(mix)

isNaN()函数,经测试发现,该函数会尝试将参数值用Number()进行转换,如果结果为“非数值”则返回true,否则返回false。

2、递增递减操作符(包括前置和后置)、一元正负符号操作符

这些操作符适用于任何数据类型的值,针对不同类型的值,该操作符遵循以下规则(经过对比发现,其规则与Number()规则基本相同):

  • (1)如果是包含有效数字字符的字符串,先将其转换为数字值(转换规则同Number()),在执行加减1的操作,字符串变量变为数值变量。
  • (2)如果是不包含有效数字字符的字符串,将变量的值设置为NaN,字符串变量变成数值变量。
  • (3)如果是布尔值false,先将其转换为0再执行加减1的操作,布尔值变量编程数值变量。
  • (4)如果是布尔值true,先将其转换为1再执行加减1的操作,布尔值变量变成数值变量。
  • (5)如果是浮点数值,执行加减1的操作。
  • (6)如果是对象,先调用对象的valueOf()方法,然后对该返回值应用前面的规则。如果结果是NaN,则调用toString()方法后再应用前面的规则。对象变量变成数值变量。

3、 加法运算操作符

加号运算操作符在Javascript也用于字符串连接符,所以加号操作符的规则分两种情况:

  • 如果两个操作值都是数值,其规则为:
    • (1)如果一个操作数为NaN,则结果为NaN
    • (2)如果是Infinity+Infinity,结果是Infinity
    • (3)如果是-Infinity+(-Infinity),结果是-Infinity
    • (4)如果是Infinity+(-Infinity),结果是NaN
    • (5)如果是+0+(+0),结果为+0
    • (6)如果是(-0)+(-0),结果为-0
    • (7)如果是(+0)+(-0),结果为+0
  • 如果有一个操作值为字符串,则:
    • 如果两个操作值都是字符串,则将它们拼接起来
    • 如果只有一个操作值为字符串,则将另外操作值转换为字符串,然后拼接起来
    • 如果一个操作数是对象、数组或者布尔值,则调用toString()方法取得字符串值,然后再应用前面的字符串规则。对于undefined和null,分别调用String()显式转换为字符串。

在加法运算中,如果有一个操作值为字符串类型,则将另一个操作值转换为字符串,最后连接起来。

4、 乘除、减号运算符、取模运算符

这些操作符针对的是运算,所以他们具有共同性:如果操作值之一不是数值,则被隐式调用Number()函数进行转换。具体每一种运算的详细规则请参考ECMAScript中的定义。

5、 逻辑操作符(!、&&、||)

  • 逻辑非(!)操作符首先通过Boolean()函数将它的操作值转换为布尔值,然后求反。
  • 逻辑与(&&)操作符,如果一个操作值不是布尔值时,遵循以下规则进行转换:
    • (1)如果第一个操作数经Boolean()转换后为true,则返回第二个操作值,否则返回第一个值(不是Boolean()转换后的值)
    • (2)如果有一个操作值为null,返回null
    • (3)如果有一个操作值为NaN,返回NaN
    • (4)如果有一个操作值为undefined,返回undefined
  • 逻辑或(||)操作符,如果一个操作值不是布尔值,遵循以下规则:
    • (1)如果第一个操作值经Boolean()转换后为false,则返回第二个操作值,否则返回第一个操作值(不是Boolean()转换后的值)
    • (2)对于undefined、null和NaN的处理规则与逻辑与(&&)相同

6、 关系操作符(<, >, <=, >=)

与上述操作符一样,关系操作符的操作值也可以是任意类型的,所以使用非数值类型参与比较时也需要系统进行隐式类型转换:
(1)如果两个操作值都是数值,则进行数值比较
(2)如果两个操作值都是字符串,则比较字符串对应的字符编码值
(3)如果只有一个操作值是数值,则将另一个操作值转换为数值,进行数值比较
(4)如果一个操作数是对象,则调用valueOf()方法(如果对象没有valueOf()方法则调用toString()方法),得到的结果按照前面的规则执行比较
(5)如果一个操作值是布尔值,则将其转换为数值,再进行比较
注:NaN是非常特殊的值,它不和任何类型的值相等,包括它自己,同时它与任何类型的值比较大小时都返回false。

7、 相等操作符(==)

相等操作符会对操作值进行隐式转换后进行比较:
(1)如果一个操作值为布尔值,则在比较之前先将其转换为数值
(2)如果一个操作值为字符串,另一个操作值为数值,则通过Number()函数将字符串转换为数值
(3)如果一个操作值是对象(包括数组,对象所有的引用类型的值),另一个不是,则调用对象的valueOf()方法,得到的结果按照前面的规则进行比较
(4)null与undefined是相等的
(5)如果一个操作值为NaN,则相等比较返回false
(6)如果两个操作值都是对象,则比较它们是不是指向同一个对象

举例说明:

[1,2]+[3,4] == “1,23,4”

[1,2,{}] == “1,2,[object, Object]”

[]+[] == ‘’

[]+{} == ‘[object Object]’

{}+{} == NaN (在firefox中,javascript把第一个{}解释成空的代码块,并忽略了它,NaN其实就是+{}的计算结果,这里的+号不是二元运算符,而是一元运算符,作用是将它后面的操作数转换成数字)

{}+{} == “[object Object][object Object]”

+”3.65” == 3.65

++[[]][+[]]+[+[]] == ‘10’

process.cwd()

获取node命令启动路径,其值与代码所在位置无关,即运行node命令时所在的文件夹的绝对路径

File System模块使用相对路径读写文件时,参考的就是这个路径。适用于开发命令行程序时,读取命令启动位置目录的文件。

__dirname

被执行的js所在的文件夹的绝对路径

__filename

被执行的js的绝对路径

require()

只有在require时才使用相对路径(./,../)的引用方法。require的使用效果是跟__dirname的效果相同,不会因为启动脚本的目录不一样而改变。

实例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
G://
- angular/
- server/
- main.js
- api.js
- spider/
- mono.js
- one.js

运行代码:
var path = require('path');
console.log(__dirname);
console.log(__filename);
console.log(process.cwd());
console.log(path.resolve('./'));

以下结果都是运行main.js所得:

  • 在server文件夹下,输出结果是:

    1
    2
    3
    4
    G:\angular\server
    G:\angular\server\main.js
    G:\angular\server
    G:\angular\server
  • 在angular文件下,输出结果是:

    1
    2
    3
    4
    G:\angular\server
    G:\angular\server\main.js
    G:\angular
    G:\angular
  • 获取相同目录下的其他文件

    • path.join(path.dirname(__filename) + ‘/api.js’)
    • path.resolve(__dirname + ‘/api.js’)

此时,不管是在angular文件夹下,还是server文件夹下,两者的结果都是G:\angular\server\api.js

  • 获取相邻目录下的文件
    • 在windows下path.resolve(__dirname , '../spider/mono.js,两种文件夹下运行结果一致;
    • 在Linux的centos7下,两者结果不一致。angular文件夹下会报错
      • 如果在angular文件夹下,使用path.resolve(path.resolve('./') + '/spider/mono.js'),或者path.resolve(process.cwd() + '/spider/mono.js')
      • 如果在server文件夹下,使用path.resolve(__dirname , '../spider/mono.js');

path.resolve和path.join的区别

  • path.join 全部给定的 path 片段连接到一起,并规范化生成的路径。如果连接后的路径字符串是一个长度为零的字符串,则代表返回 ‘.’,表示当前工作目录。不会对’/‘进行解读
  • path.resolve 把一个路径或路径片段的序列解析为一个绝对路径,会将’/‘当成根目录。

两者都是正常解读’..’和’.’。

假设在G://angular;

  • path.join(‘a’, ‘b’, ‘c’) // ‘a/b/c’
  • path.join(‘a’, ‘b’, ‘..’, ‘c’) // ‘a/c’
  • path.resolve(‘a’, ‘b’, ‘..’, ‘c’) // G://a/c
  • path.resolve(‘/a’, ‘/b’, ‘..’, ‘c’) // G://c

urllib

python内置的标准库模块

urlopen

1
2
3
4
5
6
7
import urllib.request
import urllib.parse

url = 'https://api.github.com/users/github'
f = urllib.request.urlopen(url)
// html或者json数据都可以解析
print(f.read().decode('utf-8'))

利用type(f)可以查看输出响应的类型,<class 'http.client.HTTPResponse'>,它是一个HTTPResponse类型的对象,它主要包括read()、readinto()、getheader(name)、getheaders()、fileno()等方法,以及msg、version、status、reason、debuglevel、closed等属性。

  • 调用read()方法可以得到返回的网页内容
  • 调用status属性可以得到返回结果的状态码,如200代表请求成功,404代表网页未找到等
  • getheaders()响应头信息

urlopen()也可以传递参数:

1
urllib.request.urlopen(url, data=None, [timeout, ]*, cafile=None, capath=None, cadefault=False, context=None)
  • data
    data参数是可选的。如果要添加该参数,并且如果它是字节流编码格式的内容,即bytes类型,则需要通过bytes()方法转化。另外,如果传递了这个参数,则它的请求方式就不再是GET方式,而是POST方式。
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
import urllib.parse
import urllib.request

data = bytes(urllib.parse.urlencode({'word': 'hello'}), encoding='utf8')
response = urllib.request.urlopen('http://httpbin.org/post', data=data)
print(response.read().decode('utf-8')) // 如果不加decode('utf-8')

打印结果:
{
"args": {},
"data": "",
"files": {},
"form": {
"word": "hello"
},
"headers": {
"Accept-Encoding": "identity",
"Connection": "close",
"Content-Length": "10",
"Content-Type": "application/x-www-form-urlencoded",
"Host": "httpbin.org",
"User-Agent": "Python-urllib/3.6"
},
"json": null,
"origin": "61.148.29.62",
"url": "http://httpbin.org/post"
}

传递的参数{word: ‘hello’},它需要被转码成bytes(字节流)类型。其中转字节流采用了bytes()方法,该方法的第一个参数需要是str(字符串)类型,需要用urllib.parse模块里的urlencode()方法来将参数字典转化为字符串;第二个参数指定编码格式,这里指定为utf8。

  • timeout
    用于设置超时时间,单位为秒,意思就是如果请求超出了设置的这个时间,还没有得到响应,就会抛出异常。如果不指定该参数,就会使用全局默认时间。它支持HTTP、HTTPS、FTP请求。
1
2
3
4
5
6
7
import urllib.request

response = urllib.request.urlopen('http://httpbin.org/get', timeout=0.1)
print(response.read())

运行结果:
超时报错,服务器没有响应,于是抛出了URLError异常。该异常属于urllib.error模块,错误原因是超时。

可以通过设置这个超时时间来控制一个网页如果长时间未响应,就跳过它的抓取。这可以利用try except语句来实现,相关代码如下:

1
2
3
4
5
6
7
8
9
import socket
import urllib.request
import urllib.error

try:
response = urllib.request.urlopen('http://httpbin.org/get', timeout=0.1)
except urllib.error.URLError as e:
if isinstance(e.reason, socket.timeout):
print('TIME OUT')

更多urlopen的用法

request

简单的请求可以使用urlopen,如果需要在请求中加入Headers等信息,就可以使用更强大的Request类来构建。

1
2
3
4
5
import urllib.request

request = urllib.request.Request('https://python.org')
response = urllib.request.urlopen(request)
print(response.read().decode('utf-8'))

依然是使用urlopen发送请求,只不过参数不再是url,而是一个request类型的对象。

class urllib.request.Request(url, data=None, headers={}, origin_req_host=None, unverifiable=False, method=None)

  • url 这是必选,其他事可选
  • data 必须是bytes(字节流)类型,如果是字典,可以先用urllib.parse模块里的urlencode()编码。
  • headers 可以在构造请求时通过headers参数直接构造,也可以通过调用请求实例的add_header()方法添加。
  • origin_req_host 指的是请求方的host名称或者IP地址。
  • unverifiable表示这个请求是否是无法验证的,默认是False,意思就是说用户没有足够权限来选择接收这个请求的结果。
  • method是一个字符串,用来指示请求使用的方法,比如GET、POST和PUT等
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    from urllib import request, parse

    url = 'http://httpbin.org/post'
    headers = {
    'User-Agent': 'Mozilla/4.0 (compatible; MSIE 5.5; Windows NT)',
    'Host': 'httpbin.org'
    }
    dict = {
    'name': 'Germey'
    }
    data = bytes(parse.urlencode(dict), encoding='utf8')
    req = request.Request(url=url, data=data, headers=headers, method='POST')
    response = request.urlopen(req)
    print(response.read().decode('utf-8'))

    url即请求URL,headers中指定了User-Agent和Host,参数data用urlencode()和bytes()方法转成字节流。另外,指定了请求方式为POST。

或者使用add_header()

1
2
req = request.Request(url=url, data=data, method='POST')
req.add_header('User-Agent', 'Mozilla/4.0 (compatible; MSIE 5.5; Windows NT)')

Requests

由urllib3提供支持,Requests抽象了大量的程式化的代码,使得http请求比使用内置urllib库更简单

1
2
3
4
5
6
7
// 安装requests,pip install requests

import requests
from pprint import pprint

r = requests.get(url)
pprint(r.json())

什么是Referer?

Referer[sic] 请求头字段允许由客户端指定资源的 URI 来自于哪一个请求地址,这对服务器有好处(应该是 “referrer” 这个字段拼错了)。Referer 请求头让服务器能够拿到请求资源的来源,可以用于分析用户的兴趣爱好、收集日志、优化缓存等等。同时也让服务器能够发现过时的和错误的链接并及时维护。

Referer是HTTP请求header的一部分,当浏览器向web服务器发送请求的时候,头信息里包含有Referer。Request Headers中有一个Referer字段,对应的信息表示一个来源。

Referer的作用?

  • 防盗链

如果我在www.a.com里有一个www.b.com链接,那么访问www.b.com时,它的Request Headers中有Referer: www.a.com,可以利用这个来防止盗链了,比如我只允许我自己的网站访问我自己的图片服务器,那我的域名是www.a.com,那么图片服务器每次取到Referer来判断一下是不是我自己的域名www.a.com,如果是就继续访问,不是就拦截。

  • 获取访问来源,统计访问流量的来源和搜索的关键词

像CNZZ、百度统计等可以通过Referer统计访问流量的来源和搜索的关键词(包含在URL中)等等,方便站长们有针性对的进行推广和SEO。

防盗链

nginx配置防盗链

利用valid_referers指令防盗链,HTTPReferer头信息是可以通过程序来伪装生成的,所以通过Referer信息防盗链并非100%可靠,但是,它能够限制大部分的盗链。

valid_referers [none|blocked|server_names]
默认值:none 使用环境:server,location 该指令会根据Referer Header头的内容分配一个值为0或1给变量$invalid_referer。

该指令的参数的值:

  • none:表示无Referer值
  • blocked:表示Referer值被防火墙进行伪装
  • server_names:表示一个或者多个主机名称
1
2
3
4
5
6
7
8
location ~*\.(gif|jpg|png)$ {
root /home/www/spider/;
valid_referers *.juphy.cn;
if ($invalid_referer) {
rewrite ^/soso.com // 当没有referer进行访问时,会自动跳转到这个链接。
return 403;
}
}

关于nginx.conf的配置,if判断注意保持空格。

反盗链

  • 通过nodejs设置referer
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    let http = require('http');
    let option = {
    hostname: 'img.juphy.cn',
    path: '/mono/images/2018-8-22.jpg'
    };

    http.get(option, res => {
    let imgdata = '';
    res.on('data', chunk => {
    imgdata += chunk;
    });
    res.on('end', () => {
    console.log(imgdata);
    })
    });

【原文】5 Ways to Make HTTP Requests in Node.js

HTTP模块,http或者https库 需要解析JSON格式

1
2
3
4
5
6
7
8
9
10
11
12
const https = require('https');
https.get('https://api.nasa.gov/planetary/apod?api_key=DEMO_KEY', res => {
let data = '';
res.on('data', chunk => {
data += chunk;
})
res.on('end', () => {
console.log(1, JSON.parse(data))
})
}).on('error', err => {
console.log('Error:' + err.message);
})

request库,如果使用promise可以调用request-promise库

1
2
3
4
5
6
7
const request = require('request');
request('https://api.nasa.gov/planetary/apod?api_key=DEMO_KEY', {json: true}, (err, res, body) => {
if (err) {
return console.log(err)
}
console.log(2, body);
})

request-promise库 request的promise操作

1
2
3
4
5
6
7
const requestPromise = require('request-promise');
requestPromise('https://api.nasa.gov/planetary/apod?api_key=DEMO_KEY')
.then(res => {
console.log(3, res);
}).catch(err => {
console.log(err);
});

axios

Axios是一个基于promise的HTTP客户端,可以用于浏览器和Node.js

axios库 默认情况下,Axios可以解析JSON响应

1
2
3
4
5
6
7
const axios = require('axios');
axios.get('https://api.nasa.gov/planetary/apod?api_key=DEMO_KEY')
.then(res => {
console.log(4, res.data);
}).catch(err => {
console.log(err);
})

axios库的all,发起并发请求

1
2
3
4
5
6
7
8
9
axios.all([
axios.get('https://api.nasa.gov/planetary/apod?api_key=DEMO_KEY&date=2017-08-03'),
axios.get('https://api.nasa.gov/planetary/apod?api_key=DEMO_KEY&date=2017-08-02'),
]).then(axios.spread((res1, res2) => {
console.log('res1', res1.data);
console.log('res2', res2.data);
})).catch(err => {
console.log(err);
})

superAgent 默认解析JSON响应,能进行链式调用

1
2
3
4
5
6
7
8
9
const superagent = require('superagent');
superagent.get('https://api.nasa.gov/planetary/apod')
.query({api_key: 'DEMO_KEY', date: '2017-08-02'})
.end((err, res) => {
if (err) {
return console.log(err)
}
console.log(5, res.body);
})

Got 类似于promise,比较轻量,不像request那样臃肿

1
2
3
4
5
6
7
const got = require('got');
got('https://api.nasa.gov/planetary/apod?api_key=DEMO_KEY', {json: true})
.then(res => {
console.log(res.body);
}).catch(err => {
console.log(err.reponse.body);
});

node-fetch库与window.fetch 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
const fetch = require('node-fetch');

*plain text or html

fetch('https://github.com/')
.then(res => res.text())
.then(body => console.log(body));

*json

fetch('https://api.github.com/users/github')
.then(res => res.json())
.then(json => console.log(json));

*catch network error

fetch('http://domain.invalid/')
.catch(err => console.error(err));

*stream

fetch('https://assets-cdn.github.com/images/modules/logos_page/Octocat.png')
.then(res => {
return new Promise((resolve, reject) => {
const dest = fs.createWriteStream('./octocat.png');
res.body.pipe(dest);
res.body.on('error', err => {
reject(err);
});
dest.on('finish', () => {
resolve();
});
dest.on('error', err => {
reject(err);
});
});
});

更多fetch的用法