文章目录
Scrapy爬虫
了解Scrapy爬虫框架
Scrapy是一个爬虫框架而非功能函数库,简单地说,它是一个半成品,可以帮助用户简单快速地部署一个专业的网络爬虫。Scrapy爬虫框架主要由引擎(Engine)、调度器(Scheduler)、下载器(Downloader)、Spiders、Item Pipelines、下载器中间件(Downloader Middlewares)、Spider中间件(Spider Middlewares)这7个组件构成。
引擎(Engine)
引擎负责控制数据流在系统所有组件中的流向,并在不同的条件时触发相对应的事件。这个组件相当于爬虫的“大脑”,是整个爬虫的调度中心
调度器(Scheduler)
调度器从引擎接受请求并将它们加入队列,以便之后引擎需要它们时提供给引擎。初始爬取的URL和后续在网页中获取的待爬取的URL都将放入调度器中,等待爬取,同时调度器会自动去除重复的URL。如果特定的URL不需要去重也可以通过设置实现,如post请求的URL
下载器(Downloader)
下载器的主要功能是获取网页内容,提供给引擎和Spiders
Spiders
Spiders是Scrapy用户编写用于分析响应,并提取Items或额外跟进的URL的一个类。每个Spider负责处理一个(一些)特定网站
Item Pipelines
Item Pipelines主要功能是处理被Spiders提取出来的Items。典型的处理有清理、验证及持久化(例如存取到数据库中)。当网页被爬虫解析所需的数据存入Items后,将被发送到项目管道(Pipelines),并经过几个特定的次序处理数据,最后存入本地文件或数据库
下载器中间件(Downloader Middlewares)
下载器中间件是一组在引擎及下载器之间的特定钩子(specific hook),主要功能是处理下载器传递给引擎的响应(response)。下载器中间件提供了一个简便的机制,通过插入自定义代码来扩展Scrapy功能。通过设置下载器中间件可以实现爬虫自动更换user-agent、IP等功能
Spider中间件(Spider Middlewares)
Spider中间件是一组在引擎及Spiders之间的特定钩子(specific hook),主要功能是处理Spiders的输入(响应)和输出(Items及请求)。Spider中间件提供了一个简便的机制,通过插入自定义代码来扩展Scrapy功能。各组件之间的数据流向如图所示
scrapy基本流程
- 引擎打开一个网站,找到处理该网站的Spiders,并向该Spiders请求第一个要爬取的URL。
- 引擎将爬取请求转发给调度器,调度指挥进行下一步。
- 引擎向调度器获取下一个要爬取的请求。
- 调度器返回下一个要爬取的URL给引擎,引擎将URL通过下载中间件(请求方向)转发给下载器
- 一旦网页下载完毕,下载器生成一个该网页的响应,并将其通过下载中间件(返回响应方向)发送给引擎。
- 引擎从下载器中接收到响应并通过Spider中间件(输入方向)发送给Spiders处理。
- Spiders处理响应并返回爬取到的Items及(跟进)新的请求给引擎。
- 引擎将(Spiders返回的)爬取到的Items给Item Pipeline,将(Spiders返回的)请求给调度器。
- 从第2步重复直到调度器中没有更多的URL请求,引擎关闭该网站。
熟悉Scrapy常用命令
Scrapy通过命令行进行控制,Scrapy提供了多种命令,用于多种目的,并且每个命令都接收一组不同的参数和选项
除了全局命令外,Scrapy还提供了专用于项目的项目命令
创建Scrapy爬虫项目
命令行下面使用
scrapy startproject <project_name> [project_dir]
这里是引用
下面的步骤生成一TipDMSpider的爬虫,爬取目标网页
http://www.tipdm.org/bdrace/notices/
将内容保存到excel中
新建项目
命令行执行
scrapy startproject TipDMSpider
可以看到创建的文件夹内容如下
爬虫的主要目标就是从网页这一非结构化的数据源中提取结构化的数据。TipDMSpider项目最终的目标是解析出文章的标题(title)、时间(time)、正文(text)、浏览数(view_count)等数据。Scrapy提供Item对象来完成解析数据转换为结构化数据的功能。TipDMSpider项目提取的信息最终将存储至csv文件与数据库。使用pandas库将Items中的数据转换为DataFrame会更方便处理
修改items脚本
爬虫的主要目标就是从网页这一非结构化的数据源中提取结构化的数据。TipDMSpider项目最终的目标是解析出文章的标题(title)、时间(time)、正文(text)、浏览数(view_count)等数据。Scrapy提供Item对象来完成解析数据转换为结构化数据的功能
分析目标页需要获取的内容如下
修改items的代码如下
class TipdmspiderItem(scrapy.Item):
title = scrapy.Field()
time = scrapy.Field()
source = scrapy.Field()
text = scrapy.Field()
pass
修改setting
在setting下开启ITEM_PIPELINES后才能使得到的items进入pipeline处理
ITEM_PIPELINES = {
'TipDMSpider.pipelines.TipdmspiderPipeline': 300,
}
HTTPCACHE_ENABLED = True
HTTPCACHE_DIR = 'C:\\Users\\zeng\\PycharmProjects\\spider\\TipDMSpider\\TipDMSpider'
项目的默认settings脚本中共有25个设置
修改piplines
items的内容将会流向piplines,piplines的作用就是讲获取到的数据进行清理、验证后持久化
import pandas as pd
class TipdmspiderPipeline(object):
def process_item(self, item, spider):
data = pd.DataFrame(dict(item))
data.to_csv('output_data.csv', mode='a+', index=False, sep='|', header=False)
return item
编写spider脚本
使用命令行在当前路径下新建一个爬虫入口文件
scrapy genspider tipdm www.tipdm.org
在spiders文件夹中可以看到生成出来的tipdm.py
定制中间件
中间件能够完成很多任务,比如实现IP代理,改变下载频率,限制最大爬取深度、筛选未成功响应、去重等等
定制下载器中间件
在项目中的middlewares.py文件中,是中间件的代码,在最底下添加两个class,是我们自定定义的中间件,第一个为随机代理,另外一个为随机ua
from scrapy.utils.python import to_bytes
from six.moves.urllib.parse import unquote
from six.moves.urllib.parse import urlunparse
import base64
import random
try:
from urllib2 import _parse_proxy
except ImportError:
from urllib.request import _parse_proxy
class ProxyMiddleware(object):
def _basic_auth_header(self, username, password):
user_pass = to_bytes(
'%s:%s' % (unquote(username), unquote(password)),
encoding='latin-1')
return base64.b64encode(user_pass).strip()
def process_request(self, request, spider):
PROXIES = [
"http://zeng:123456@149.133.121.158:808/",
]
url = random.choice(PROXIES)
orig_type = ""
proxy_type, user, password, hostport = _parse_proxy(url)
proxy_url = urlunparse((proxy_type or orig_type, hostport, '', '', '', ''))
if user:
creds = self._basic_auth_header(user, password)
else:
creds = None
request.meta['proxy'] = proxy_url
print("当前使用代理:", url)
if creds:
request.headers['Proxy-Authorization'] = b'Basic ' + creds
from scrapy.downloadermiddlewares.useragent import UserAgentMiddleware
user_agent_list = [
"Mozilla/5.0 (X11; U; Linux x86_64; zh-CN; rv:1.9.2.10) Gecko/20100922 Ubuntu/10.10 (maverick) Firefox/3.6.10",
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36",
]
class UASpiderMiddleware(UserAgentMiddleware):
def __init__(self, user_agent):
self.user_agent = user_agent_list
# 设置User-Agent
def process_request(self, request, spider):
agent = random.choice(self.user_agent)
request.headers['User-Agent'] = agent
print('当前User-Agent:', agent)
激活中间件
在项目的setting.py文件中,还有很多其他信息,现在只需要关注以下几个,首先激活刚加入的两个自定义中间件,然后打开默认的一个refer中间件,找到DOWNLOADER_MIDDLEWARES,打开注释,写入如下内容
DOWNLOADER_MIDDLEWARES = {
'TipDMSpider.middlewares.TipdmspiderDownloaderMiddleware': 543,
'TipDMSpider.middlewares.ProxyMiddleware': 544,
'scrapy.spidermiddlewares.referer.DefaultReferrerPolicy': 545,
'TipDMSpider.middlewares.UASpiderMiddleware': 546,
'scrapy.downloadermiddlewares.robotstxt.RobotsTxtMiddleware': 0 # 关闭内置下载器中间件robots
}
声明不遵守爬虫协议,设置下载延迟为5秒
# Obey robots.txt rules
ROBOTSTXT_OBEY = True
DOWNLOAD_DELAY = 5
打开缓存的注释,并修改成如下
HTTPCACHE_ENABLED = True
HTTPCACHE_DIR = 'C:\\Users\\zeng\\PycharmProjects\\spider\\TipDMSpider\\TipDMSpider'
测试整个项目
在命令行执行
scrapy crawl tipdm
启动爬虫,可以看到中间的输出,和最后的csv文件
题外话,使用自定义url去重
在项目中编写一个去重的py文件,duplicate_filter.py,内容如下
from scrapy.dupefilter import BaseDupeFilter
from scrapy.utils.request import request_fingerprint
"""
1. 根据配置文件找到 DUPEFILTER_CLASS = 'xianglong.dupe.MyDupeFilter'
2. 判断是否存在from_settings
如果有:
obj = MyDupeFilter.from_settings()
否则:
obj = MyDupeFilter()
"""
class MyDupeFilter(BaseDupeFilter):
def __init__(self):
self.record = set()
@classmethod
def from_settings(cls, settings):
return cls()
#:return: True表示已经访问过;False表示未访问过
def request_seen(self, request):
ident = request_fingerprint(request)
if ident in self.record:
print('已经访问过了', request.url)
return True
print("=================新发现的url=================")
self.record.add(ident)
def open(self): # can return deferred
pass
def close(self, reason): # can return a deferred
pass
在setting中使用整个filter
DUPEFILTER_CLASS = 'TipDMSpider.duplicate_filter.MyDupeFilter'
在我们的tipdm.py中,也就是spider文件的入口,去掉
dont_filter=True
这样去重class才会生效
# 提取每一个分页中的每一条新闻
def parse_url(self, response):
# 取出ul标签
urls = response.xpath("//a[@class='titA']/@href").extract()
# 循环取出里面的li链接
for url in urls:
article_url = "http://www.tipdm.org" + url
yield Request(article_url, callback=self.parse_item)
for url in urls:
article_url = "http://www.tipdm.org" + url
# yield Request(article_url, callback=self.parse_item, dont_filter=True)
yield Request(article_url, callback=self.parse_item)