开发环境为 Python3.6,Scrapy 版本 2.4.x ,Gerapy 版本 0.9.x ,爬虫项目全部内容索引目录
看懂Python爬虫框架,所见即所得一切皆有可能
既然是标准化作业,就必须要有一个标准化的模板。
依照此本文的模板可以做到无脑复制到Scrapy项目中,将每个spider文件修改 spider 目录下的每一个项目 py 文件即可。
只需要修改列表业页和详情页中需要每个页面抓取的部分,确定他们的标签和属性在模板中进行替换,即可实现单一网站整体快速的数据抓取和采集。
不过在这之前还是要确认好标准化作业中处理的爬虫抓取目标,精准定位。不仅抓取的数据可以便于项目中的应用,也方便后期的爬虫脚本的维护。
1. 创建项目
新建一个文件夹存放所有的 Scrapy 项目,打开命令行创建项目。
scrapy startproject 你的项目名(不支持中文)
2. 创建爬虫脚本
进入创建的项目文件夹创建爬虫脚本,即每个 spiders 目录下的文件。
创建每个spider文件注意事项。
例如:http://www.cctd.com.cn/ 写成
scrapy genspider www_cctd_com_cn " "
scrapy.cfg
项目的配置文件,可以暂时忽略。未来部署分布式会使用到。
items.py
保存爬取到的数据的容器,其使用方法和python字典类似, 用于创建抓取内容的字段属性,比如新闻内容设置title、url、content属性用于保存数据结构。
pipelines.py
用来执行保存数据的操作。用于根据items.py指定的字段提取的数据进行保存到相应的表格或者数据仓库中。
settings.py
比较重要的配置文件,用于配置各种设置。
middlewares.py
用于在执行爬虫脚本时进行相关操作的控制脚本,例如启动浏览器,更换代理IP等等操作。
spiders下的py文件
是未来写好的爬虫执行脚本。
1. 配置 items.py
import scrapy
class StudydataItem(scrapy.Item):
title = scrapy.Field() # 文章标题
url = scrapy.Field() # 文章链接url
thumbImg = scrapy.Field() # 文章封面
publishTime = scrapy.Field() # 文章发布日期
content = scrapy.Field() # 文章正文
channel_name = scrapy.Field() # 文章所属频道
py_name = scrapy.Field() # 脚本名称
web_name = scrapy.Field() # 网站名称
2.配置 middlewares.py
直接在底部添加对应内容。
2.1 添加中间件随机更换Header
用于随机更换浏览器头信息,防止被反爬的基础修改,这里是在 setting.py 文件中做好的浏览器header进行随机选择。
# 添加Header和IP类
from scrapy.downloadermiddlewares.useragent import UserAgentMiddleware
from scrapy.utils.project import get_project_settings
settings = get_project_settings()
class RotateUserAgentMiddleware(UserAgentMiddleware):
def process_request(self, request, spider):
referer = request.url
if referer:
request.headers["referer"] = referer
USER_AGENT_LIST = settings.get('USER_AGENT_LIST')
user_agent = random.choice(USER_AGENT_LIST)
if user_agent:
request.headers.setdefault('user-Agent', user_agent)
print(f"user-Agent:{
user_agent}")
2.2 设置更换代理IP
如果网站反爬不厉害的话这部分可以忽略。
# 添加随机更换IP代理类(根据实际IP代理情况进行修改获取方式和更改方式)
import random
import sys
import requests
sys.path.append('.')
class MyProxyMiddleware(object):
def process_request(self, request, spider):
url = "这里放购买的代理API地址,进行解析后使用代理访问"
html = requests.get(url).text
ip_list = html.split("\r\n")[:-1]
proxy = random.choice(ip_list)
request.meta['proxy'] = 'http://' + proxy
3.配置 pipelines.py
mongodb数据存储,毕竟抓上亿的数据感觉还是mongodb好用。这里为了安全起见为mongdb设置一下密码吧。
# 文件头添加
import pymongo
from scrapy.utils.project import get_project_settings
settings = get_project_settings()
这里是读取 Settings.py 中的MongoDB的相应设置操作,如果未在 Settings.py 中设置,这里也可以直接定义。
将settings[“xxxxx”]中的内容替换成你的配置即可。
# 添加必备包和加载设置
import pymongo
from scrapy.utils.project import get_project_settings
settings = get_project_settings()
class StudydataPipeline(object):
# class中全部替换
def __init__(self):
host = settings["MONGODB_HOST"]
port = settings["MONGODB_PORT"]
dbname = settings["MONGODB_DATABASE"]
sheetname = settings["MONGODB_TABLE"]
username = settings["MONGODB_USER"]
password = settings["MONGODB_PASSWORD"]
# 创建MONGODB数据库链接
client = pymongo.MongoClient(host=host, port=port, username=username, password=password)
# 指定数据库
mydb = client[dbname]
# 存放数据的数据库表名
self.post = mydb[sheetname]
def process_item(self, item, spider):
data = dict(item)
# 数据写入
self.post.insert(data)
return item
4. 配置 settings.py
4.1 ROBOTSTXT_OBEY 机器人协议
文件中20-21行,建议修改Fasle否则很多网站没办法折腾。
# Obey robots.txt rules
ROBOTSTXT_OBEY = True
4.2 DOWNLOADER_MIDDLEWARES 下载中间件
# Enable or disable downloader middlewares
# See https://docs.scrapy.org/en/latest/topics/downloader-middleware.html
DOWNLOADER_MIDDLEWARES = {
'项目名.middlewares.StudydataDownloaderMiddleware': 543,
'scrapy.contrib.downloadermiddleware.useragent.UserAgentMiddleware': None,
'项目名.middlewares.RotateUserAgentMiddleware': 400, # 更换Header优先级
# '项目名.middlewares.MyProxyMidleware': 300, #更换IP优先级默认注销掉
}
4.3 Item piplines 数据据处理
67-69行代码默认是注释的要解开,否则数据无法写入数据仓库。
# Configure item pipelines
# See https://docs.scrapy.org/en/latest/topics/item-pipeline.html
ITEM_PIPELINES = {
'项目名.pipelines.StudydataPipeline': 300,
}
4.4 mongodb数据仓库配置
# 添加 设置MONGODB数仓
MONGODB_HOST = "你数据仓库的IP"
MONGODB_PORT = 数据仓库的端口号
MONGODB_DBNAME = "存储数据的数据仓库的仓库名"
MONGODB_SHEETNAME = "存储数据的数据仓库的表名"
MONGODB_USER = "mongdodb你设置的用户名"
MONGODB_PASSWORD = "mongdodb你设置的密码"
5. 随机浏览器header
可以根据自己需要进行增减。
# 添加 设置浏览器Header
USER_AGENT_LIST = [
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.1 (KHTML, like Gecko) Chrome/22.0.1207.1 Safari/537.1",
"Mozilla/5.0 (X11; CrOS i686 2268.111.0) AppleWebKit/536.11 (KHTML, like Gecko) Chrome/20.0.1132.57 Safari/536.11",
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/536.6 (KHTML, like Gecko) Chrome/20.0.1092.0 Safari/536.6",
"Mozilla/5.0 (Windows NT 6.2) AppleWebKit/536.6 (KHTML, like Gecko) Chrome/20.0.1090.0 Safari/536.6",
"Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.1 (KHTML, like Gecko) Chrome/19.77.34.5 Safari/537.1",
"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/536.5 (KHTML, like Gecko) Chrome/19.0.1084.9 Safari/536.5",
"Mozilla/5.0 (Windows NT 6.0) AppleWebKit/536.5 (KHTML, like Gecko) Chrome/19.0.1084.36 Safari/536.5",
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/536.3 (KHTML, like Gecko) Chrome/19.0.1063.0 Safari/536.3",
"Mozilla/5.0 (Windows NT 5.1) AppleWebKit/536.3 (KHTML, like Gecko) Chrome/19.0.1063.0 Safari/536.3",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_0) AppleWebKit/536.3 (KHTML, like Gecko) Chrome/19.0.1063.0 Safari/536.3",
"Mozilla/5.0 (Windows NT 6.2) AppleWebKit/536.3 (KHTML, like Gecko) Chrome/19.0.1062.0 Safari/536.3",
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/536.3 (KHTML, like Gecko) Chrome/19.0.1062.0 Safari/536.3",
"Mozilla/5.0 (Windows NT 6.2) AppleWebKit/536.3 (KHTML, like Gecko) Chrome/19.0.1061.1 Safari/536.3",
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/536.3 (KHTML, like Gecko) Chrome/19.0.1061.1 Safari/536.3",
"Mozilla/5.0 (Windows NT 6.1) AppleWebKit/536.3 (KHTML, like Gecko) Chrome/19.0.1061.1 Safari/536.3",
"Mozilla/5.0 (Windows NT 6.2) AppleWebKit/536.3 (KHTML, like Gecko) Chrome/19.0.1061.0 Safari/536.3",
"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/535.24 (KHTML, like Gecko) Chrome/19.0.1055.1 Safari/535.24",
"Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/535.24 (KHTML, like Gecko) Chrome/19.0.1055.1 Safari/535.24"
]
实现一个列表页中根据列表的顺序进行遍历循环抓取每个url并访问然后存储到数据仓库中。
这个模板根据自己的需要进行调整即可。
1. 设置第三方加载包
import scrapy
from 项目名.items import 对应项目的item(就是items.py里的class名)
import json
import time
import re
from urllib import parse
from .parse_detail import *
from random import shuffle
from gerapy_auto_extractor.extractors import *
from scrapy.utils.project import get_project_settings
settings = get_project_settings()
2. 设置基础数据属性
name = 'xxxxxx' # 这个地方是默认生成用于启动脚本的name不需要修改
allowed_domains = [] # 作用域,这里之前定义成空格,这里就不需要操作了
web_name = "xxxxxx网" # 抓取的网站中文名称,用于爬虫数据管理
start_menu = [
# 抓取栏目类别标记,比如写成 XXX频道 这样
# 也可以把同一CSS样式的放一起通过同一个 parse 处理
[
# 这里放处理好的数据字典
# 数据格式举例
{
"channel_name": "xxxxx频道-xxxxx列表", "url": "https://www.xxxx.com/list.html", },
],
]
3.start_requests方法重写
重写 start_requests 方法,依据2中的start_menu格式进行处理。
def start_requests(self):
# start_menu 中的1个列表元素对应 parse_list 列表元素。
# 意思是对应每个列表元素执行对应的 parse 方法进行页面解析处理。
parse_list = [
self.parse1,
]
# 非API接口方法,增加随机处理模式
start_menu_list = self.start_menu
shuffle(start_menu_list) # 打乱每个栏目种的顺序
for each_menu_num in range(len(self.start_menu)):
new_list = self.start_menu[each_menu_num]
shuffle(new_list) # 打乱每个栏目中的抓取目标
for each_menu in self.start_menu[each_menu_num]:
url = each_menu["url"]
channel_name = each_menu["channel_name"]
parse_function = parse_list[each_menu_num]
yield scrapy.Request(
url=url,
meta={
'url': url,
'channel_name': channel_name,
},
callback=parse_function
)
4. parse 列表页数据处理
# 抓取内容的配置 parse_list根据定义匹配
def parse1(self, response):
# 抓取选择页面的列表信息,获取的内容均为list,要求长度必须一直否则会出错
# 这里不抓取日期,在正文里抓取日期
Item_title = response.xpath('//标签[@class="属性"]/a/text()').extract() # 文章标题列表
Item_url = response.xpath('//标签[@class="属性"]/a/@href').extract() # 文章链接列表
Item_thumbImg = response.xpath('//标签[@class="属性"]/a/img/@src').extract() # 文章封面图片列表
for each in range(len(Item_title)):
item = 对应项目的item() # 这里对应Item里的类名
item['title'] = Item_title[each].strip() # 内容标题
item['url'] = parse.urljoin(response.url, Item_url[each]) # 拼接正文url
# item['thumbImg'] = parse.urljoin(response.url, Item_thumbImg[each]) # 拼接图片url
item['publishTime'] = ""
item['thumbImg'] = ""
item['channel_name'] = response.meta["channel_name"]
item['web_name'] = self.web_name
item["py_name"] = self.name + ".py"
yield scrapy.Request(item['url'], callback=self.parse_detail, meta={
'item': item})
# 抓取内容的配置 parse_list根据定义匹配
def parse1(self, response):
data = extract_list(response.text)
for each in range(len(data)):
item = 对应项目的item() # 这里对应Item里的类名
item['title'] = data[each]["title"].strip() # 内容标题
item['url'] = parse.urljoin(response.url, data[each]["url"]) # 拼接正文url
# item['thumbImg'] = parse.urljoin(response.url, Item_thumbImg[each]) # 拼接图片url
item['publishTime'] = ""
item['thumbImg'] = ""
item['channel_name'] = response.meta["channel_name"]
item['web_name'] = self.web_name
item["py_name"] = self.name + ".py"
yield scrapy.Request(item['url'], callback=self.parse_detail, meta={
'item': item})
5.详情页内容回调方法
将抓取内容详情页的方法统一交付到 parse_detail.py 文件处理。
# 详情页在parse_detail.py中处理
def parse_detail(self, response):
item = ProcessContent(self, response)
yield item
在目录下新建文件 parse_detail.py。
用于处理抓取的内容的日期和正文数据,其中使用 gerapy_auto_extractor 模块偷懒直接获取正文内容和日期(前提正文内容里要有)
# coding:utf-8
__author__ = 'Mr.数据杨'
__explain__ = '抓取文章正文内容' \
'1.DateTimeProcess_Str 处理日期内容,如果始终没有日期可以抓取默认当天日期' \
'2.ProcessContent 处理正文内容' \
'3.提取unHtmlContent(无格式的文字内容)' \
'4.提取content(含有CSS样式的内容)'
import re
from gerapy_auto_extractor.extractors import extract_detail
import time
# 处理日期数据函数
def DateTimeProcess_Str(text):
time_text = str(text)
if ("年" or "月" or "日") in time_text:
time_text = re.findall('\d{4}年\d{2}月\d{2}', time_text)[0]
time_text = re.sub(r'[年月]', '-', time_text)
time_text = re.sub(r'[日]', '', time_text)
return time_text
elif "-" in time_text:
time_text = re.findall('\d{4}-\d{2}-\d{2}', time_text)[0]
return time_text
else:
return time.strftime('%Y-%m-%d')
# 判断内容是否None
def None2Str(text):
if text is None:
return ''
else:
return text
def ProcessContent(self, response):
# 设置详情页的内容
item = response.meta['item']
data = extract_detail(response.text)
# 处理详情页的时间,如果始终没有获取到时间默认当天日期
if data["datetime"] is None:
item['publishTime'] = DateTimeProcess_Str(item['publishTime'])
else:
item['publishTime'] = DateTimeProcess_Str(data["datetime"])
# 处理详情页带格式,这里整个页面进行抓取
item['content'] = ""
# 这里对应的是列表抓取文件下的内容,分不同的网站设置不同的详情抓取方法。
if item['web_name'] == "xxxxxx":
if '属性="xxxxxx"' in response.text and len(None2Str(item['content'])) < 5:
item['content'] = response.xpath('//标签[@属性="xxxxxx"]').extract_first()
# 通用的方法页面数据无法采集默认抓取整个页面
if len(item['content']) < 5:
item['content'] = response.xpath('//body').extract_first()
return item
1.启动脚本
scrapy crawl (spider下文件名,启动脚本)
scrapy crawlall (spider全部,启动脚本,需要进行配置)
有些网站的程序员丧心病狂到一定程度10个页面9种样式这种,由于我们不可能每个页面都打开看一下详情页的CSS格式,因此有个通用的解决办法。
db.你的表名.find({content:/body/})