-
Notifications
You must be signed in to change notification settings - Fork 5
Expand file tree
/
Copy pathtab.go
More file actions
375 lines (354 loc) · 10.5 KB
/
Copy pathtab.go
File metadata and controls
375 lines (354 loc) · 10.5 KB
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
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
package crawler
import (
"context"
"github.com/chromedp/cdproto/cdp"
"github.com/chromedp/cdproto/dom"
"github.com/chromedp/cdproto/fetch"
"github.com/chromedp/cdproto/network"
"github.com/chromedp/cdproto/page"
"github.com/chromedp/chromedp"
"log"
"net/url"
"strings"
"sync"
"time"
)
type Tab struct {
ctx context.Context
cancel context.CancelFunc
browser *browser
LoadTimeOut int //秒
WaitTime int //毫秒
AcceptDialog bool //true表示在js弹出窗中按确认, false表示取消(默认)
resourceparams resourceParams
}
func (self *Tab) DisableCrawlResource() *resourceParams {
self.resourceparams.disableResource = true
return &self.resourceparams
}
func (self *Tab) Close() {
self.cancel()
self = nil
}
//默认为0,为0时取browser的时间
func (self *Tab) SetLoadTimeOut(loadtime int) *Tab {
self.LoadTimeOut = loadtime
return self
}
//默认为0,为0时取browser的时间
func (self *Tab) SetWaitTime(waittime int) *Tab {
self.WaitTime = waittime
return self
}
//获取pdf字节数组
func (self *Tab) GetPdfBytes(url string) ([]byte, error) {
var er error
var pdfBuffer []byte
er = chromedp.Run(self.ctx,
chromedp.ActionFunc(func(ctx context.Context) error {
var err error
pdfBuffer, _, err = page.PrintToPDF().WithPrintBackground(true).Do(ctx)
return err
}),
)
return pdfBuffer, er
}
//todo 暂时未测试
//页面截图
func (self *Tab) GetSnapShot(url string) (string, error) {
var er error
var rawimage string
er = chromedp.Run(self.ctx,
chromedp.ActionFunc(func(ctx context.Context) error {
var err error
rawimage, err = page.CaptureSnapshot().Do(ctx)
return err
}),
)
return rawimage, er
}
//获取页面元素文本
func (self *Tab) GetDocument() (res []byte, err error) {
err = chromedp.Run(self.ctx,
chromedp.ActionFunc(func(ctx context.Context) error {
doc, er := dom.GetDocument().Do(ctx)
if er != nil {
return er
}
str, err := dom.GetOuterHTML().WithNodeID(doc.NodeID).Do(ctx)
if err != nil {
return err
}
dom.QuerySelectorAll(doc.NodeID, "script")
res = []byte(str)
return nil
}),
)
return res, err
}
//跳转一个页面,并执行脚本,返回数据给v
func (self *Tab) NavigateEvaluate(rawUrl string, rule string, v interface{}) (err error) {
_, err = url.Parse(rawUrl)
if err != nil {
return ERR_INVALID_URL
}
if !strings.HasPrefix(strings.TrimSpace(rawUrl), "http") {
rawUrl = "http://" + rawUrl
}
err = chromedp.Run(self.ctx, chromedp.Navigate(rawUrl))
if err != nil {
return err
}
err = chromedp.Run(self.ctx, chromedp.Evaluate(rule, &v))
return err
}
//在当前页面执行脚本
func (self *Tab) Evaluate(rule string, v interface{}) error {
return chromedp.Run(self.ctx, chromedp.Evaluate(rule, &v))
}
//建立一个分页对象
func (self *Tab) NewPagenation(pagerule string, spdierrule string, data interface{}) (page *pagenation, err error) {
var p pagenation
p.Datalist = data
p.newtabfunc = self.browser.NewTab
p.Pagenationrule = pagerule
p.mtab = self
p.SpiderRule = spdierrule
return &p, nil
}
//跳转页面并获取各种页面信息
func (self *Tab) Navigate(rawUrl string) (doc DocumentInfo, err error) {
_, err = url.Parse(rawUrl)
if err != nil {
return doc, ERR_INVALID_URL
}
if !strings.HasPrefix(strings.TrimSpace(rawUrl), "http") {
rawUrl = "http://" + rawUrl
}
doc = DocumentInfo{
Resources: make(map[string]Resource, 0),
}
var res sync.Map
var start = time.Now()
//计算加载时长
defer func() {doc.LoadTime = int(time.Since(start).Milliseconds())}()
if self.LoadTimeOut == 0 {
self.LoadTimeOut = Crawler_LoadTimeOut
}
var done = make(chan struct{})
var documentReceived = false
{
var blockpatterns = make([]*fetch.RequestPattern, 0)
if self.resourceparams.disableResource {
if self.resourceparams.blockImage {
blockpatterns = append(blockpatterns, &fetch.RequestPattern{
ResourceType: network.ResourceTypeImage,
RequestStage: "Request",
})
}
if self.resourceparams.blockJs {
blockpatterns = append(blockpatterns, &fetch.RequestPattern{
ResourceType: network.ResourceTypeScript,
RequestStage: "Request",
})
}
if self.resourceparams.blockCss {
blockpatterns = append(blockpatterns, &fetch.RequestPattern{
ResourceType: network.ResourceTypeStylesheet,
RequestStage: "Request",
})
}
if self.resourceparams.blockFont {
blockpatterns = append(blockpatterns, &fetch.RequestPattern{
ResourceType: network.ResourceTypeFont,
RequestStage: "Request",
})
}
if self.resourceparams.blockMedia {
blockpatterns = append(blockpatterns, &fetch.RequestPattern{
ResourceType: network.ResourceTypeMedia,
RequestStage: "Request",
})
}
}
var actions = make([]chromedp.Action, 0)
if self.resourceparams.disableResource && len(blockpatterns) > 0 {
actions = append(actions, fetch.Enable().WithPatterns(blockpatterns))
}
actions = append(actions, network.Enable(), chromedp.Navigate(rawUrl))
var ctxErr error
go func() {
chromedp.ListenTarget(self.ctx, func(ev interface{}) {
//退出则放弃
ctxErr = self.ctx.Err()
if ctxErr != nil && err == context.Canceled{
log.Println("已退出")
return
}
switch event := ev.(type) {
case *network.EventResponseReceived:
go func(evt *network.EventResponseReceived) {
var resp = evt.Response
if !documentReceived && evt.Type == network.ResourceTypeDocument {
documentReceived = true //第一个下载成功的document为未渲染的源码
doc.StatusCode = int(resp.Status)
doc.Ip = resp.RemoteIPAddress //网站IP
doc.Port = int(resp.RemotePort) //网站端口
doc.RespUrl = resp.URL
doc.ResourceType = evt.Type
if resp.Timing != nil {
doc.DnsTime = int(resp.Timing.DNSEnd - resp.Timing.DNSStart)
doc.ResponseTime = int(resp.Timing.ReceiveHeadersEnd)
}
} else {
//页面资源类型的舍弃
if strings.HasPrefix(strings.ToLower(strings.TrimSpace(resp.URL)), "data:"){
return
}
val, ok := res.Load(resp.URL)
if ok {
res.Store(resp.URL, resourceMap{
tp: evt.Type,
requestID: evt.RequestID,
referUrl: val.(resourceMap).referUrl,
statuscode: int(evt.Response.Status),
})
}else{
res.Store(resp.URL, resourceMap{
tp: evt.Type,
requestID: evt.RequestID,
statuscode: int(evt.Response.Status),
})
}
}
}(event)
//通过fetch屏蔽资源
case *fetch.EventRequestPaused:
go func(evt *fetch.EventRequestPaused) {
nctx := chromedp.FromContext(self.ctx)
lctx := cdp.WithExecutor(self.ctx, nctx.Target)
_ = fetch.FailRequest(evt.RequestID, network.ErrorReasonAborted).Do(lctx)
}(event)
case *network.EventRequestWillBeSent:
go func(evt *network.EventRequestWillBeSent) {
//页面资源类型,XHR异步数据的舍弃
if strings.HasPrefix(strings.ToLower(strings.TrimSpace(evt.Request.URL)), "data:") || evt.Type == network.ResourceTypeXHR{
return
}
if self.resourceparams.disableResource {
if self.resourceparams.blockImage && evt.Type == network.ResourceTypeImage {
return
}
if self.resourceparams.blockJs && evt.Type == network.ResourceTypeScript {
return
}
if self.resourceparams.blockCss && evt.Type == network.ResourceTypeStylesheet {
return
}
if self.resourceparams.blockFont && evt.Type == network.ResourceTypeFont {
return
}
if self.resourceparams.blockMedia && evt.Type == network.ResourceTypeMedia {
return
}
}
var refer, ok = event.Request.Headers["Referer"]
if ok {
val, ok := res.Load(evt.Request.URL)
if ok {
res.Store(evt.Request.URL, resourceMap{
referUrl: val.(resourceMap).referUrl,
})
}else{
res.Store(evt.Request.URL, resourceMap{referUrl:refer.(string)})
}
}
}(event)
case *page.EventJavascriptDialogOpening:
//弹窗自动关闭,不太好用,不能正确匹配确认或者取消
go func() {
t := page.HandleJavaScriptDialog(self.AcceptDialog)
nctx := chromedp.FromContext(self.ctx)
lctx := cdp.WithExecutor(self.ctx, nctx.Target)
go func() {
_ = chromedp.Run(lctx, t)
}()
}()
}
})
//network.enable必须在navigate之前
errr := chromedp.Run(self.ctx, actions...)
if errr != nil {
log.Println("标签执行任务失败" + errr.Error())
}
done <- struct{}{}
}()
}
select {
case <-time.After(time.Second * time.Duration(self.LoadTimeOut)):
//加载失败
if !documentReceived || doc.Ip == "" {
return doc, ERR_URL_LOAD_FAIL
}
//超时
return doc, ERR_URL_TIMEOUT
case <-done:
//加载失败
if !documentReceived || doc.Ip == "" {
return doc, ERR_URL_LOAD_FAIL
}
//强制等待时间
if self.WaitTime == 0 {
self.WaitTime = Crawler_WaitTime
}
time.Sleep(time.Duration(self.WaitTime) * time.Millisecond)
}
//为资源赋值responsebody
nctx := chromedp.FromContext(self.ctx)
lctx := cdp.WithExecutor(self.ctx, nctx.Target)
err = chromedp.Run(lctx, chromedp.ActionFunc(func(ctx context.Context) error {
//暂时不选择并行,因为有丢失的问题,当前采用单协程重试机制
res.Range(func(key, value interface{}) bool {
var tp = value.(resourceMap).tp
if _, ok := Default_ResourceType_Allow[tp]; !ok {
return true //不存在则返回并继续遍历
}
var newval = Resource{
Type: tp,
Url: key.(string),
StatusCode: value.(resourceMap).statuscode,
Referer: value.(resourceMap).referUrl,
}
body, er := network.GetResponseBody(value.(resourceMap).requestID).Do(lctx)
if er == nil && body != nil && len(body) > 0 {
newval.Value = body
}
if newval.Value == nil || len(newval.Value) == 0 {
//bs, tp, code, er := SimpleGet(key.(string))
//if er != nil {
// log.Println(key.(string), "资源错误: ", er.Error())
//} else {
// newval.Value = bs
// newval.StatusCode = code
// newval.Type = ConvertResourceType(tp)
//}
}
doc.Resources[key.(string)] = newval
return true
})
return nil
}))
return doc, err
}
// 获取当前页面的所有链接
func (self *Tab) GetAllLinks() ([]string, error) {
var list = make([]string, 0)
err := self.Evaluate(`
var ls = [];
for(i=0;i<document.links.length;i++){
ls.push(document.links[i].href);
}
ls;
`, &list)
return list, err
}