跳到主要内容

31 篇博文 含有标签「技术」

查看所有标签

3. 服务发现

· 阅读需 2 分钟

3.1 设置Pod的hostname与subdomain

# 编写Yaml文件
apiVersion: v1
kind: Service
metadata:
name: default-subdomain
spec:
selector:
app: nginx
clusterIP: None
ports:
- name: nginx
port: 80
targetPort: 80
---
apiVersion: v1
kind: Pod
metadata:
name: nginx-hostname
labels:
app: nginx
spec:
hostname: nginx-1
subdomain: default-subdomain
containers:
- image: nginx
name: nginx
imagePullPolicy: IfNotPresent

解析Service Name

# 创建busybox应用
kubectl run busybox --image=busybox:1.28 --image-pull-policy=IfNotPresent --command sleep 36000

# 执行解析操作
kubectl exec -it busybox -- nslookup default-subdomain

查看node的角色

kubectl get nodes

3.2 HostAliases

# 编写Yaml文件
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment
labels:
app: deployment-nginx
spec:
replicas: 4
selector:
matchLabels:
app: pod-nginx
template:
metadata:
labels:
app: pod-nginx
spec:
hostAliases:
- ip: "127.0.0.1"
hostnames:
- "foo.local"
- "bar.local"
- ip: "10.1.2.3"
hostnames:
- "foo.remote"
- "bar.remote"
containers:
- name: nginx
image: nginx:1.7.6
ports:
- containerPort: 80

3.3 Pod的DNS设置

# 编写Yaml文件
apiVersion: v1
kind: Pod
metadata:
namespace: default
name: dns-example
spec:
containers:
- name: test
image: nginx
dnsPolicy: "None"
dnsConfig:
nameservers:
- 1.2.3.4
searches:
- ns1.svc.cluster-domain.example
- my.dns.search.suffix
options:
- name: ndots
value: "2"
- name: edns0

# 在容器test的/etc/resolv.conf文件中获取以下内容
nameserver 1.2.3.4
search ns1.svc.cluster-domain.example my.dns.search.suffix
options ndots:2 edns0

crm 项目部署流程

· 阅读需 13 分钟

crm 项目部署流程

  • 配置 nginx,提供反向代理功能。将 80 端口的请求,转发给 django 的 8000 端口;
  • uwsgi + django 启动后端进程,部署在 8000 端口,处理用户的动态逻辑,包括登录、注册、查询等 curd(增删改查)操作
  • mariadb(MySQL 数据库),进行数据导出、导入操作
  • 虚拟环境的创建
  • supervisor 进程管理工具 ,防止 uwsgi 突然崩溃,supervisor 能够自动启动 uwsgi

配置 uwsgi

部署 django 项目使用的命令 python3 manage.py runserver 其实是调用 wsgiref 这个 Python 内置的 wsgi 服务器,性能很低。

wsgi 实际上是运行一个 socket 服务端,便于程序员调试 django 程序,它是单线程,单进程,性能不高。

在 Linux 服务器线上,主流的部署形式是 uwsgi 对 django 进行启动,支持多进程,多线程,以及各种优化。

uwsgi 并发性更好,因为它是 C 写的一个基于 uwsgi 协议运行的高性能 Web 服务器。

image-20200207150724321

服务器中环境比较复杂,为了不影响其他项目的运行,项目部署一般在虚拟的 Python 环境中进行。

因此,首先要配置一个 Python 的虚拟环境。在配置之前,要查看是否安装了 virtualenv:

[root@localhost etc]# which virtualenv/usr/bin/which: no virtualenv in (/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/root/bin:/etc/python36/bin:/opt/python36/bin)

若出现上面的提示,说明需要先安装:

pip3 install -i https://pypi.tuna.tsinghua.edu.cn/simple virtualenv

进入想要存放虚拟环境的文件夹,创建虚拟 Python 环境:

[root@localhost etc]# mkdir /opt/venvs[root@localhost etc]# cd /opt/venvs/[root@localhost venvs]# virtualenv --no-site-packages --python=python3 venv_crm[root@localhost venvs]# cd venv_crm/[root@localhost venv_crm]# [root@localhost venv_crm]# source ./bin/activate(venv_crm) [root@localhost venv_crm]#

以后的配置,都会在这个虚拟环境中进行。

虚拟环境激活后,安装 uwsgi 工具:

(venv_crm) [root@localhost venv_crm]# pip3 install -i https://pypi.tuna.tsinghua.edu.cn/simple uwsgi

上传 crm 代码

把我们的 crm 项目打包成 zip,在 Xshell 上通过 lrzsz 工具上传至 Linux。

rz

若没有 lrzsz 工具,可能会无法发送,需要先安装这个工具:

yum install lrzsz

若是 mac 用户,可以使用 scp 命令传输。

收到 crm 项目的 zip 压缩文件后,使用 unzip 命令解压缩。当然,要先安装 unzip 工具:

(venv_crm) [root@localhost ~]# yum install unzip -y(venv_crm) [root@localhost ~]# unzip CRM.zip

安装 crm 项目依赖的环境

安装 django

进入 crm 项目第一层文件夹,尝试启动 crm 项目:

(venv_crm) [root@localhost ~]# cd CRM(venv_crm) [root@localhost CRM]# lsapp01  CRM  manage.py  rbac  utils(venv_crm) [root@localhost CRM]# python3 manage.py runserver

运行出错,错误信息为:

ImportError: Couldn't import Django. Are you sure it's installed and available on your PYTHONPATH environment variable? Did you forget to activate a virtual environment?

这是因为虚拟环境中没有 django。兵来将挡水来土掩,安装 django 就是了:

(venv_crm) [root@localhost CRM]# pip3 install -i https://pypi.tuna.tsinghua.edu.cn/simple django==1.11.9

安装 pymysql

安装好 django,继续尝试启动 crm 项目:

(venv_crm) [root@localhost CRM]# python3 manage.py runserver

程序再次报错,这次的报错信息是:

ModuleNotFoundError: No module named 'pymysql'

看来我们还需要安装 pymysql 模块:

(venv_crm) [root@localhost CRM]# pip3 install -i https://pypi.tuna.tsinghua.edu.cn/simple pymysql

安装 django-multiselectfield

继续,尝试启动 crm 项目:

(venv_crm) [root@localhost CRM]# python3 manage.py runserver

有报错了,报错信息为:

ModuleNotFoundError: No module named 'multiselectfield'

这里提示的是没有安装 multiselectfield 模块。但主要注意的是,包的名字是 django-multiselectfield 而不是直接的 multiselectfield:

(venv_crm) [root@localhost CRM]# pip3 install -i https://pypi.tuna.tsinghua.edu.cn/simple django-multiselectfield

一键安装 crm 依赖环境

除了像我们前面逐次尝试逐个安装的方式外,我们还可以通过 pip 的 freeze 功能批量导出和导入所有模块。

在我们编写 crm 项目的操作系统中输入命令,批量导出模块信息至 requirements.txt 文件中:

pip3 freeze > requirements.txt

在 cmd 所在文件夹会出现一个 requirements.txt 文件,里面有所有的模块名和版本号信息。

把这个文件发送到 Linux 系统中,通过命令批量导入依赖的包:

pip install -r requirements.txt

配置数据库

安装 MariaDB(MySQL)

安装好各种 crm 依赖的环境之后,crm 项目依然无法正常运行:因为我们还没有安装 MySQL 数据库。

在 CentOS 中,由于 MySQL 已经开始收费,有开源组织创建了一个完全一模一样的数据库,就是 MariaDB 数据库。

可以使用 yum 直接安装 MariaDB:

(venv_crm) [root@localhost CRM]# yum install mariadb-server mariadb -y

通过 yum 安装的 MariaDB 数据库服务可以通过 systemctl 命令启动:

(venv_crm) [root@localhost CRM]# systemctl start mariadb

为测试 MariaDB 运行状态,我们可以尝试登录。MariaDB 的用法同 MySQL 完全一致,甚至连接命令使用的也是 mysql:

(venv_crm) [root@localhost CRM]# mysql -uroot -p

初始密码默认为空,直接回车即可进入 MariaDB。使用命令修改密码为 123:

MariaDB [(none)]> set password = password('123');

若数据库名称和密码会发生变化,还需要修改 settings.py 中数据库部分的配置。

导出和导入数据库数据

在 Windows 中通过命令导出数据库中的数据:

mysqldump -uroot -p crm > crm.sql

把导出的 crm.sql 文件传到 Linux 中。创建并进入 crm 数据库,使用命令导入数据:

MariaDB [(none)]> create database crm;Query OK, 1 row affected (0.00 sec)MariaDB [(none)]> use crm;Database changedMariaDB [crm]> source ~/crm.sql

可以查看一些表,是不是有数据导入:

MariaDB [crm]> show tables;+----------------------------+| Tables_in_crm              |+----------------------------+| app01_campuses             || app01_classlist            || app01_classlist_teachers   || app01_consultrecord        || app01_courserecord         || app01_customer             || app01_customer_class_list  || app01_enrollment           || app01_studyrecord          || app01_userinfo             || app01_userinfo_roles       || auth_group                 || auth_group_permissions     || auth_permission            || auth_user                  || auth_user_groups           || auth_user_user_permissions || django_admin_log           || django_content_type        || django_migrations          || django_session             || rbac_menu                  || rbac_permission            || rbac_role                  || rbac_role_permissions      |+----------------------------+25 rows in set (0.00 sec)MariaDB [crm]> select * from rbac_role;+----+-----------+| id | role_name |+----+-----------+|  2 | CEO       ||  3 | 销售      ||  4 | 讲师      |+----+-----------+3 rows in set (0.00 sec)MariaDB [crm]>

配置 settings.py

再次调试 crm ,应该就可以正常运行了。将其配置在 0.0.0.0:8000,并尝试通过浏览器访问,部署之前,或许需要清空防火墙列表:

(venv_crm) [root@localhost CRM]# iptables -F(venv_crm) [root@localhost CRM]# python3 manage.py runserver 0.0.0.0:8000

虽然访问被禁止,但已经能看见 django 的大黄页,至少说明我们成功连接到了 Linux 的 django。禁止访问的原因是,我们没有在 settings.py 中指定 ALLOWED_HOSTS。

img

ctrl + c 终止运行中的 django 项目。使用 vim 命令编辑 settings.py 文件:

(venv_crm) [root@localhost CRM]# vim CRM/settings.py

找到 ALLOWED_HOSTS,在列表中添加 ‘*’

ALLOWED_HOSTS = ['*']

保存并退出,再次启动 django 项目,我们就可以访问我们的 crm 项目了。

img

uwsgi 启动 django

虽然刚刚我们已经成功运行好 django 项目。但是我们前面讨论过,django 使用的是 python 自带的 wsgiref,性能较低。平时的测试足够用,但并不适合线上的高并发环境。所以我们需要使用 uwsgi,使用多进程的方式启动 django。

配置 uwsgi.ini 文件

进入 crm 项目的第一层文件夹,在此处创建一个 uwsgi.ini 文件:

(venv_crm) [root@localhost CRM]# pwd/root/CRM(venv_crm) [root@localhost CRM]# vim uwsgi.ini

在刚刚创建的文件中写入如下配置信息:

[uwsgi]# Django-related settings# the base directory (full path)# 填入你crm项目的第一层绝对路径chdir           = /root/CRM# Django's wsgi file# 这个wsgi.py文件,在第二层的crm目录下module          = CRM.wsgi# the virtualenv (full path)# 填写虚拟环境的绝对路径home            = /opt/venvs/venv_crm# process-related settings# mastermaster          = true# maximum number of worker processes# 定义uwsgi的工作进程数,优化公式是 2 * cpu_核数 + 1 processes       = 3# the socket (use the full path to be safe# 这个socket参数是把你的crm启动在一个基于uwsgi协议的socket链接上,用户无法直接访问了# 启动在socket链接上,就只能用nginx通过uwsgi协议反向代理,用户无法直接访问了# 保护后端进程的安全,以及高性能 # 这个socket也就是crm启动的地址和端口 socket          = 0.0.0.0:8000# ... with appropriate permissions - may be needed# chmod-socket    = 664# clear environment on exitvacuum          = true

启动 uwsgi

使用 uwsgi 命令,指定 uwsgi.ini 配置文件,启动 crm 程序,要注意找到 uwsgi 的位置:

(venv_crm) [root@localhost CRM]# uwsgi --ini uwsgi.ini

启动成功后,发现提示网页无法正常工作:

img

这是因为浏览器发送的是 http 请求,基于的是 http 协议;而 uwsgi 遵循的是 uwsgi 协议。两个协议不一致,就好比两个人,一个人说中文,一个人说英语,当然是没法正常交流的。这时,我们就需要找一个翻译。对于我们的服务器而言,这个翻译就是 nginx。

nginx 配置和静态文件托管

配置 nginx 反向代理

首先,找到 nginx.conf 文件。通过 yum 安装的 nginx,配置文件的位置为 /etc/nginx/nginx.conf;通过源码编译安装的 nginx,配置文件在指定安装路径的 conf 文件夹下。然后,修改配置文件中的反向代理参数,设置如下:

server {    listen       80 default_server;    listen       [::]:80 default_server;    server_name  _;    root         /usr/share/nginx/html;    # Load configuration files for the default server block.    include /etc/nginx/default.d/*.conf;    location / {    	# proxy_pass 处理的是 http 请求转发    	# 这里要用基于 uwsgi 的转发参数        uwsgi_pass 0.0.0.0:8000;        include uwsgi_params;    }    error_page 404 /404.html;    	location = /40x.html {    }    error_page 500 502 503 504 /50x.html;    	location = /50x.html {    }}

修改好配置文件,保存并退出。平滑重启 nginx,并启动 uwsgi 服务:

(venv_crm) [root@localhost CRM]# nginx -s reload(venv_crm) [root@localhost CRM]# uwsgi --ini uwsgi.ini

浏览器中输入服务器的 IP 地址。注意不要去 8000 端口,因为 nginx 监听的是 80 端口。如果访问 8000 端口,依然是直接访问的 uwsgi,还是无法正常访问。

静态文件托管

当我们直接输入 IP 地址访问时,访问成功。但遇到一个问题 -- 我们丢失了全部的样式,或者说,静态文件全部失效了。

img

这是因为 uwsgi 默认不会为我们解析静态文件。我们需要把静态文件托管给 nginx。

首先,在 django 项目的 settings.py 文件中,添加一个如下的配置,这个配置的意义是指定一个路径,准备存放即将导出的 crm 项目中的静态文件(注意要使用绝对路径,且不要放在 /root 路径下):

STATIC_ROOT = '/tmp/crm_static'

保存退出,使用命令导出 crm 项目中的静态文件至刚刚指定的文件夹中:

(venv_crm) [root@localhost CRM]# python3 manage.py collectstatic

查看我们指定的静态文件存放的路径,发现静态文件已经成功提出出来:

(venv_crm) [root@localhost CRM]# ls /tmp/crm_static/admin  adminLTE  auth  __init__.py  rbac

提取出静态文件后,我们需要在 nginx 的配置中指定静态文件所在的位置。首先当然是打开 nginx.conf 配置文件:

(venv_crm) [root@localhost CRM]# vim /etc/nginx/nginx.conf

在 server 中原来的 location 下添加一条新的 location 配置:

location / {    uwsgi_pass 0.0.0.0:8000;    include uwsgi_params;}location /static {	alias /tmp/crm_static;}

这条配置的意思是给路径做一个别名,当发送来 /static 的请求时,到我们指定的静态文件存放位置返回静态文件。这个过程由 nginx 自动执行,不需要像后端发送请求。

静态文件,别名的修改原理图如下:

image-20200207163337987

保存并退出配置,平滑重启 nginx,启动 uwsgi 服务:

(venv_crm) [root@localhost CRM]# nginx -s reload(venv_crm) [root@localhost CRM]# uwsgi --ini uwsgi.ini

此时,我们就可以直接输入 IP,正常访问我们的 crm 项目了。

img

Supervisor 进程管理工具

其实,我们已经完成了 crm 项目的部署。但是有这样一个问题:如果 uwsgi 因为某种原因崩溃了,而我们又没有及时发现。但是用户看来,会觉得网站很不好用。这时,我们就可以使用 Supervisor 进程管理工具。当后端进程崩溃时,Supervisor 会自动检测并自动重启 uwsgi 进程。

首先,还是要安装 Supervisor 工具:

(venv_crm) [root@localhost CRM]# yum install supervisor -y

紧接着,使用命令生成 Supervisor 配置文件,定义管理 crm 的任务:

(venv_crm) [root@localhost CRM]# echo_supervisord_conf > /etc/supervisor.conf

修改配置文件,在最底行,输入如下内容,Supervisor 其实也就是帮助用户执行命令而已:

[program:crm]command=/opt/venvs/venv_crm/bin/uwsgi --ini  /root/CRM/uwsgi.ini    ; 程序启动命令,绝对路径autostart=true       ; 在supervisord启动的时候也自动启动startsecs=10         ; 启动10秒后没有异常退出,就表示进程正常启动了,默认为1秒autorestart=true     ; 程序退出后自动重启,可选值:[unexpected,true,false],默认为unexpected,表示进程意外杀死后才重启stopasgroup=true     ;默认为false,进程被杀死时,是否向这个进程组发送stop信号,包括子进程killasgroup=true     ;默认为false,向进程组发送kill信号,包括子进程

第一行 program 冒号后的 crm,就管理任务的一个名字而已,随便叫什么,自己能够看的懂就好。

此时,我们就可以用命令启动 Supervisor,然后查看被管理的任务,可以通过 ps 命令查看 uwsgi 是否顺利启动:

(venv_crm) [root@localhost CRM]# supervisord -c /etc/supervisor.conf (venv_crm) [root@localhost CRM]# ps -ef | grep uwsgiroot      37003  37002  0 15:50 ?        00:00:00 /opt/venvs/venv_crm/bin/uwsgi --ini /root/CRM/uwsgi.iniroot      37005  37003  0 15:50 ?        00:00:00 /opt/venvs/venv_crm/bin/uwsgi --ini /root/CRM/uwsgi.iniroot      37006  37003  0 15:50 ?        00:00:00 /opt/venvs/venv_crm/bin/uwsgi --ini /root/CRM/uwsgi.iniroot      37007  37003  0 15:50 ?        00:00:00 /opt/venvs/venv_crm/bin/uwsgi --ini /root/CRM/uwsgi.iniroot      37009  35426  0 15:51 pts/0    00:00:00 grep --color=auto uwsgi

尝试杀死 uwsgi 进程,再使用 ps 命令查看:

(venv_crm) [root@localhost CRM]# pkill uwsgi(venv_crm) [root@localhost CRM]# ps -ef | grep uwsgiroot      37003  37002  0 15:50 ?        00:00:01 /opt/venvs/venv_crm/bin/uwsgi --ini /root/CRM/uwsgi.iniroot      37012  37003  0 15:53 ?        00:00:00 /opt/venvs/venv_crm/bin/uwsgi --ini /root/CRM/uwsgi.iniroot      37013  37003  0 15:53 ?        00:00:00 /opt/venvs/venv_crm/bin/uwsgi --ini /root/CRM/uwsgi.iniroot      37014  37003  0 15:53 ?        00:00:00 /opt/venvs/venv_crm/bin/uwsgi --ini /root/CRM/uwsgi.iniroot      37016  35426  0 15:53 pts/0    00:00:00 grep --color=auto uwsgi

可以使用 Supervisor 的任务管理命令 supervisorctl 查看、启动或终止进程:

(venv_crm) [root@localhost CRM]# supervisorctl -c /etc/supervisor.conf crm                              RUNNING   pid 37003, uptime 0:04:58supervisor> stop crmcrm: stoppedsupervisor> start crmcrm: startedsupervisor> restart crmcrm: stoppedcrm: startedsupervisor> status crmcrm                              RUNNING   pid 37054, uptime 0:00:16

大功告成,我们顺利实现 crm 的部署上线~

CrawlSpider 的基本用法

· 阅读需 7 分钟

CrawlSpider 的基本用法

CrawlSpider 是 Spider 的一个子类。Spider 是爬虫文件中爬虫类的父类。

一般来讲,子类的功能要比父类多,所以 CrawlSpider 的功能是比 Spider 更完善更强大的。

CrawlSpider 的作用:常被用作于专业实现全站数据爬取,也就是将一个页面下所有页码对应的数据进行爬取。

CrawlSpider 的基本使用:

  1. 创建一个工程

  2. cd 到这个工程根目录

  3. 创建一个基于 CrawlSpider 的爬虫文件

    scrapy genspider -t crawl SpiderName www.xxx.com
  4. 执行工程

注意:

  1. 一个链接提取器对应一个规则解析器(多个链接提取器和多个规则解析器)
  2. 在实现深度爬取的过程中需要和 scrapy.Request() 结合使用

需求:爬取校花网图片

网址 url:http://www.521609.com/daxuexiaohua/

示例代码:

import scrapy
from scrapy.linkextractors import LinkExtractor
from scrapy.spiders import CrawlSpider, Rule

class FirstSpider(CrawlSpider):
name = 'first'
# allowed_domains = ['www.521609.com']
start_urls = ['http://www.521609.com/daxuexiaohua/']

# 实例化LinkExtractor对象
# 链接提取器:根据指定规则(allow参数)在页面中进行连接(url)的提取
# allow='正则':提取链接的规则
# link = LinkExtractor(allow=r'list3\d+\.html')
link = LinkExtractor(allow=r'') #取出网站全站的链接
rules = (
# 实例化一个Rule对象
# 规则解析器:接收链接提取器提取到的链接,对其发起请求,然后根据指定规则(callback)解析数据
Rule(link, callback='parse_item', follow=True),
)
# follow=True:
# 将链接提取器 继续作用到 连接提取器提取到的页码 所对应的 页面中
def parse_item(self, response):
print(response)
# 基于response实现数据解析

问:如何将一个网站中全站所有的链接都进行爬取。

答:把正交规则写成空字符串 '' 即可。如果仅要爬取本站所有内容,需要设置 allowed_domains 为当前域名,避免爬取到其他网站。

使用 CrawlSpider 实现深度爬取

需求:爬取阳光热线问政平台最新问政的问政标题、状态和问政详细信息。

网址 url:http://wz.sun0769.com/political/index/politicsNewest?id=1&type=4&page=

分析:问政标题和状态均可在首页得到,但是详细信息需要爬取详情页内容。使用 crawl 确实可以获取到每一个详情页的网址,并获取详细信息,但是如果要将首页的问政标题和状态与详情页的详细信息匹配,需要下一番功夫。(事实上,我们可以在详情页拿到所有这三个信息,但是为了学习和训练,我们分别从首页和详情页抓取。)

我们可以使用问政编号,作为数据的标识。如此一来,可以将首页内容和详情页内容一一对应了。

基于上面的思路,我们可以编写出爬虫的源代码:

from scrapy.linkextractors import LinkExtractor
from scrapy.spiders import CrawlSpider, Rule
from sunPro.items import SunproItem, SunproDeatilItem


class SunSpider(CrawlSpider):
name = 'sun'
# allowed_domains = ['www.xxx.com']
start_urls = ['http://wz.sun0769.com/political/index/politicsNewest?id=1&type=4&page=']

rules = (
# 提取页码链接,解析每一个页码对应页面中的数据
Rule(LinkExtractor(allow=r'id=1&page=\d+'), callback='parse_item', follow=True),
# 提取详情页连接,解析详情页中的数据
Rule(LinkExtractor(allow=r'index\?id=\d+'), callback='parse_detail', follow=False),
)

def parse_item(self, response):
"""首页,用来提取标题和状态"""
li_list = response.xpath('//ul[@class="title-state-ul"]/li')
for li in li_list:
item = SunproItem()
qid = li.xpath('./span[1]/text()').extract_first().strip()
status = li.xpath('./span[2]/text()').extract_first().strip()
title = li.xpath('./span[3]/a/text()').extract_first().strip()
item['qid'] = qid
item['status'] = status
item['title'] = title
yield item

# 实现深度爬取:爬取详情页中的数据
# 1.对详情页的url进行捕获
# 2.对详情页的url发起请求获取数据
def parse_detail(self, response):
"""详情页,用来提取详细信息,实现深度爬取"""
qid = response.xpath('//div[@class="mr-three"]/div[1]/span[4]/text()').extract_first().replace('编号:', '').strip()
content = response.xpath('//div[@class="mr-three"]/div[2]/pre/text()').extract_first().strip()
item = SunproDeatilItem()
item['qid'] = qid
item['content'] = content
yield item

数据通过问政 id 进行标识,数据存储的时候,相同的问政 id 保存在一起就好。

别忘了在 items.py 中写好两个 Item 类,SunproItem 和 SunproDetailItem。

但是现在还有一个问题:首页和详情页解析出来的数据统统给了管道。但是这两种数据是不同的,处理方式也是有差异的。我们该如何在管道中区分这两种数据,并对其进行分别存储呢?

很简单,这两个 item 是不同类的实例化对象,我们只需要通过查询类名,即可对两种数据进行区分。item 类名的查询方式为:

item.__class__.__name__

通过判断类名,分别处理,根据 qid 进行一一对应存储,我这里就不详细写了:

class SunproPipeline(object):
def process_item(self, item, spider):
"""数据持久化存储,根据qid进行一一对应,就不详细写了"""
qid = item['qid']
if item.__class__.__name__ == 'SunproItem':
title = item['title']
status = item['status']
print(qid, title, status)
else:
content = item['content']
print(qid, content)
return item

CrawlSpider 结合 Spider 实现深度爬取

像上面那种,单独使用 CrawlSpider 确实能够实现深度爬取。但是我们发现,坑一个接着一个:首页和详情页的数据不容易一一对应,两个不同的 item 对象传入管道,携带的数据和数据处理方式也不同,需要区别对待。

链接获取和发送请求的过程的确是节省了,但是又多出了许多其他的麻烦,有些得不偿失。所以对于深度爬取的任务,我们更倾向于使用的方法是,通过 CrawlSpider 与 spider.Request 结合使用的方式,实现深度爬取。

这就很容易处理了,手动请求,加上请求参数传递即可实现:

import scrapy
from scrapy.linkextractors import LinkExtractor
from scrapy.spiders import CrawlSpider, Rule
from sunPro.items import SunproItem, SunproDeatilItem

class SunSpider(CrawlSpider):
name = 'sun'
# allowed_domains = ['www.xxx.com']
start_urls = ['http://wz.sun0769.com/political/index/politicsNewest?id=1&type=4&page=']

rules = (
# 提取页码链接,解析每一个页码对应页面中的数据
Rule(LinkExtractor(allow=r'id=1&page=\d+'), callback='parse_item', follow=False),
# # 提取详情页连接,解析详情页中的数据
# Rule(LinkExtractor(allow=r'index\?id=\d+'), callback='parse_detail', follow=False),
)

def parse_item(self, response):
"""首页,用来提取标题和状态"""
li_list = response.xpath('//ul[@class="title-state-ul"]/li')
for li in li_list:
item = SunproItem()
qid = li.xpath('./span[1]/text()').extract_first().strip()
status = li.xpath('./span[2]/text()').extract_first().strip()
title = li.xpath('./span[3]/a/text()').extract_first().strip()
url = 'http://wz.sun0769.com/' + li.xpath('./span[3]/a/@href').extract_first().strip()
item['qid'] = qid
item['status'] = status
item['title'] = title
yield scrapy.Request(url, callback=self.parse_detail, meta={'item': item})

def parse_detail(self, response):
item = response.meta['item']
qid = response.xpath('//div[@class="mr-three"]/div[1]/span[4]/text()').extract_first().replace('编号:', '').strip()
content = response.xpath('//div[@class="mr-three"]/div[2]/pre/text()').extract_first().strip()
item['qid'] = qid
item['content'] = content
yield item

这样,只会有一种 item 对象,里面是我们需要的所有数据。在管道中,不需要分类处理,不需要考虑首页和详情页一一对应的问题。一切都变得很简单了。