02-Python 爬虫

https://blog.csdn.net/fenglepeng/article/details/100539847
https://blog.csdn.net/fenglepeng/article/details/100541603

scrapy 框架中各组件的工作流程?

  1. 生成初始的 Requests 来爬取第一个 URLS,并且标识一个回调函数。第一个请求定义在 start_requests() 方法内默认从 start_urls 列表中获得 url 地址来生成 Request 请求,默认的回调函数是 parse 方法。回调函数在下载完成返回response时自动触发

  2. 在回调函数中,解析 response 并且返回值,返回值可以4种:

    1. 包含解析数据的字典
    2. Item对象
    3. 新的Request对象(新的Requests也需要指定一个回调函数)
    4. 或者是可迭代对象(包含Items或Request)
  3. 在回调函数中解析页面内容,通常使用 Scrapy 自带的 Selectors,但很明显你也可以使用 Beutifulsoup,lxml 或其他你爱用的。

  4. 最后,针对返回的 Items 对象将会被持久化到数据库,通过 Item Pipeline 组件存到数据库,或者导出到不同的文件(通过 Feed exports)

在 scrapy 框架中如何设置代理(两种方法)?

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
# 方式一:内置添加代理功能
# -*- coding: utf-8 -*-
import os
import scrapy
from scrapy.http import Request

class ChoutiSpider(scrapy.Spider):
name = 'chouti'
allowed_domains = ['chouti.com']
start_urls = ['https://dig.chouti.com/']

def start_requests(self):
os.environ['HTTP_PROXY'] = "http://192.168.11.11"

for url in self.start_urls:
yield Request(url=url,callback=self.parse)

def parse(self, response):
print(response)

# 方式二:自定义下载中间件
import random
import base64
import six
def to_bytes(text, encoding=None, errors='strict'):
"""Return the binary representation of `text`. If `text`
is already a bytes object, return it as-is."""
if isinstance(text, bytes):
return text
if not isinstance(text, six.string_types):
raise TypeError('to_bytes must receive a unicode, str or bytes '
'object, got %s' % type(text).__name__)
if encoding is None:
encoding = 'utf-8'
return text.encode(encoding, errors)


class MyProxyDownloaderMiddleware(object):
def process_request(self, request, spider):
proxy_list = [
{'ip_port': '111.11.228.75:80', 'user_pass': 'xxx:123'},
{'ip_port': '120.198.243.22:80', 'user_pass': ''},
{'ip_port': '111.8.60.9:8123', 'user_pass': ''},
{'ip_port': '101.71.27.120:80', 'user_pass': ''},
{'ip_port': '122.96.59.104:80', 'user_pass': ''},
{'ip_port': '122.224.249.122:8088', 'user_pass': ''},
]
proxy = random.choice(proxy_list)
if proxy['user_pass'] is not None:
request.meta['proxy'] = to_bytes("http://%s" % proxy['ip_port'])
encoded_user_pass = base64.encodestring(to_bytes(proxy['user_pass']))
request.headers['Proxy-Authorization'] = to_bytes('Basic ' + encoded_user_pass)
else:
request.meta['proxy'] = to_bytes("http://%s" % proxy['ip_port'])


#配置:
DOWNLOADER_MIDDLEWARES = {
# 'xiaohan.middlewares.MyProxyDownloaderMiddleware': 543,
}

scrapy 框架中如何实现大文件的下载?

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
from twisted.web.client import Agent, getPage, ResponseDone, PotentialDataLoss
from twisted.internet import defer, reactor, protocol
from twisted.web._newclient import Response
from io import BytesIO

class _ResponseReader(protocol.Protocol):
def __init__(self, finished, txresponse, file_name):
self._finished = finished
self._txresponse = txresponse
self._bytes_received = 0
self.f = open(file_name, mode='wb')
def dataReceived(self, bodyBytes):
self._bytes_received += len(bodyBytes)
# 一点一点的下载
self.f.write(bodyBytes)
self.f.flush()
def connectionLost(self, reason):
if self._finished.called:
return
if reason.check(ResponseDone):
# 下载完成
self._finished.callback((self._txresponse, 'success'))
elif reason.check(PotentialDataLoss):
# 下载部分
self._finished.callback((self._txresponse, 'partial'))
else:
# 下载异常
self._finished.errback(reason)
self.f.close()

scrapy 中如何实现限速?

http://scrapy-chs.readthedocs.io/zh_CN/1.0/topics/autothrottle.html

scrapy 中如何实现暂停爬虫?

https://scrapy-chs.readthedocs.io/zh_CN/1.0/topics/jobs.html?highlight=item

有些情况下,例如爬取大的站点,我们希望能暂停爬取,之后再恢复运行。Scrapy通过如下工具支持这个功能:

  • 一个把调度请求保存在磁盘的调度器
  • 一个把访问请求保存在磁盘的副本过滤器[duplicates filter]
  • 一个能持续保持爬虫状态(键/值对)的扩展

Job 路径

要启用持久化支持,你只需要通过 JOBDIR 设置 job directory 选项。
这个路径将会存储所有的请求数据来保持一个单独任务的状态(例如:一次spider爬取(a spider run))。
必须要注意的是,这个目录不允许被不同的spider 共享,甚至是同一个 spider 的不同 jobs/runs 也不行。
也就是说,这个目录就是存储一个单独 job 的状态信息。

scrapy 中如何进行自定制命令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 在spiders同级创建任意目录,如:commands
# 在其中创建'crawlall.py'文件(此处文件名就是自定义的命令)
from scrapy.commands import ScrapyCommand
from scrapy.utils.project import get_project_settings
class Command(ScrapyCommand):
requires_project = True
def syntax(self):
return '[options]'
def short_desc(self):
return 'Runs all of the spiders'
def run(self, args, opts):
spider_list = self.crawler_process.spiders.list()
for name in spider_list:
self.crawler_process.crawl(name, **opts.__dict__)
self.crawler_process.start()
# 在'settings.py'中添加配置'COMMANDS_MODULE = '项目名称.目录名称''
# 在项目目录执行命令:'scrapy crawlall'

scrapy 中如何实现的记录爬虫的深度?

DepthMiddleware 是一个用于追踪每个Request在被爬取的网站的深度的中间件。其可以用来限制爬取深度的最大深度或类似的事情。
DepthMiddleware 可以通过下列设置进行配置(更多内容请参考设置文档):

  • ‘DEPTH_LIMIT’:爬取所允许的最大深度,如果为0,则没有限制。
  • ‘DEPTH_STATS’:是否收集爬取状态。
  • ‘DEPTH_PRIORITY’:是否根据其深度对requet安排优先

scrapy 中的 pipelines 工作原理?

Scrapy 提供了 pipeline 模块来执行保存数据的操作。
在创建的 Scrapy 项目中自动创建了一个 pipeline.py 文件,同时创建了一个默认的 Pipeline 类。
我们可以根据需要自定义 Pipeline 类,然后在 settings.py 文件中进行配置即可

scrapy 的 pipelines 如何丢弃一个 item 对象?

通过 raise DropItem() 方法

简述 scrapy 中爬虫中间件和下载中间件的作用?

https://blog.csdn.net/fenglepeng/article/details/100541603

scrapy-redis 组件的作用?

实现了分布式爬虫,url去重、调度器、数据持久化

  • ‘scheduler’调度器
  • ‘dupefilter’URL去重规则(被调度器使用)
  • ‘pipeline’数据持久化

scrapy-redis 组件中如何实现的任务的去重?

  1. 内部进行配置,连接 Redis

  2. 去重规则通过 Redis 的集合完成,集合的 Key 为:key = defaults.DUPEFILTER_KEY % {'timestamp': int(time.time())}
    默认配置:DUPEFILTER_KEY = 'dupefilter:%(timestamp)s'

  3. 去重规则中将url转换成唯一标示,然后在 Redis 中检查是否已经在集合中存在

    1
    2
    3
    4
    5
    from scrapy.utils import request
    from scrapy.http import Request
    req = Request(url='http://www.cnblogs.com/wupeiqi.html')
    result = request.request_fingerprint(req)
    print(result) # 8ea4fd67887449313ccc12e5b6b92510cc53675c
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
# scrapy和scrapy-redis的去重规则(源码)
# 1. scrapy中去重规则是如何实现?
class RFPDupeFilter(BaseDupeFilter):
"""Request Fingerprint duplicates filter"""

def __init__(self, path=None, debug=False):
self.fingerprints = set()

@classmethod
def from_settings(cls, settings):
debug = settings.getbool('DUPEFILTER_DEBUG')
return cls(job_dir(settings), debug)

def request_seen(self, request):
# 将request对象转换成唯一标识。
fp = self.request_fingerprint(request)
# 判断在集合中是否存在,如果存在则返回True,表示已经访问过。
if fp in self.fingerprints:
return True
# 之前未访问过,将url添加到访问记录中。
self.fingerprints.add(fp)

def request_fingerprint(self, request):
return request_fingerprint(request)


# 2. scrapy-redis中去重规则是如何实现?
class RFPDupeFilter(BaseDupeFilter):
"""Redis-based request duplicates filter.

This class can also be used with default Scrapy's scheduler.

"""

logger = logger

def __init__(self, server, key, debug=False):

# self.server = redis连接
self.server = server
# self.key = dupefilter:123912873234
self.key = key


@classmethod
def from_settings(cls, settings):

# 读取配置,连接redis
server = get_redis_from_settings(settings)

# key = dupefilter:123912873234
key = defaults.DUPEFILTER_KEY % {'timestamp': int(time.time())}
debug = settings.getbool('DUPEFILTER_DEBUG')
return cls(server, key=key, debug=debug)

@classmethod
def from_crawler(cls, crawler):

return cls.from_settings(crawler.settings)

def request_seen(self, request):

fp = self.request_fingerprint(request)
# This returns the number of values added, zero if already exists.
# self.server=redis连接
# 添加到redis集合中:1,添加工程;0,已经存在
added = self.server.sadd(self.key, fp)
return added == 0

def request_fingerprint(self, request):

return request_fingerprint(request)

def close(self, reason=''):

self.clear()

def clear(self):
"""Clears fingerprints data."""
self.server.delete(self.key)

scrapy-redis 的调度器如何实现任务的深度优先和广度优先?


02-Python 爬虫
https://flepeng.github.io/interview-20-开发语言类-21-Python-02-Python-爬虫/
作者
Lepeng
发布于
2020年8月8日
许可协议