利用ISCSI存储技术构建IP存储网络(实战篇)

本文重点介绍如何构建一个PC构架的iSCSI存储系统,这里我们选择一个普通的、性能优良的、可支持多块磁盘的PC服务器作为iSCSI
target,并且选择一个成熟稳定的iSCSI
target软件iscsitarget,基本配置环境如表1所示:
表1

一、iSCSI的概念 iSCSI是一种在Internet协议上,特别是以太网上进行数据块传输的标准,它是一种基于IP
Storage理论的新型存储技术,该技术是将存储行业广泛应用的SCSI接口技术与IP网络技术相结合,可以在IP网络上构建SAN存储区域网,简单地说,iSCSI就是在IP网络上运行SCSI协议的一种网络存储技术。iSCSI技术最初由Cisco和IBM两家发起,并且得到了广大IP存储技术爱好者的大力支持。这几年迅速的发展壮大了起来。
对于中小企业的存储网络来说,iSCSI是个非常好的选择,首先,从技术实现来讲,iSCSI是基于IP协议的技术标准,它允许网络在TCP/IP协议上传输SCSI命令,实现了SCSI和TCP/IP协议的连接,这样用户就可以通过TCP/IP网络来构建存储区域网,它只需要不多的投资,就可以方便、快捷地对信息和数据进行交互式传输和管理。但是在iSCSI出现之前,构建存储区域网的唯一技术是利用光纤通道,而这要花费很大的建设成本,一般中小企业是无法承担的,其次iSCSI技术解决了传输效率、存储容量、兼容性、开放性、安全性等诸多问题,在使用性能上绝对不输给商业的存储系统或者光纤存储网络。
iSCSI的优势主要表现为:首先,iSCSI沿用TCP/IP协议,而TCP/IP是在网络方面最通用、最成熟的协议,且IP网络的基础建设非常完善,同时,SCSI技术是被磁盘、磁带等设备广泛采用的存储标准,这两点使iSCSI的建设费用和维护成本非常低廉;其次,iSCSI支持一般的以太网交换机而不是特殊的光纤通道交换机,从而减少了异构网络带来的麻烦;最后,iSCSI是通过IP封包传输存储命令,因此可以在整个Internet上传输,没有距离的限制。
二、FC SAN与IP SAN 当iSCSI技术出现后,通过IP技术搭建的存储网络也就应运而生,SAN技术也就出现了两种不同的实现方式,即FC
SAN与IP SAN。简单说,以光纤搭建的存储网络就是FC
SAN,以iSCSI技术搭建的存储网络叫做IP SAN。
作为SAN的两种实现方式,FC SAN与IP
SAN也各有优劣,下面从几个方面分别阐述:
? 在数据传输方式上,FC SAN与IP
SAN都是采用块协议方式来完成的。这是它们的相同点。
? 在传输速度上,就目前的传输速度而言是FC
SAN(2Gb)最快、iSCSI(1Gb)次之。
? 在传输距离上,FC
SAN理论上可以达到100公里,而事实上当传输超过50公里后,就会出现瓶颈。透过IP网络的iSCSI理论上都没有距离上的限制,
iSCSI可以进行无限长度的数据传输。
? 在管理以及维护成本上,架设FC
SAN网络需要投入很多硬件费用,并且需要特定的工具软件来操作管理,而IP
SAN构建成本低廉,由于iSCSI是通过IP网络来传输数据及分配存储资源,所以只要在现有的网络上进行管理和使用即可。这样就可以省下大笔管理费用以及培训成本。
         其实IP SAN也面临着一些不可回避的困扰:
         首先,基于IP SAN的网络存储还没有得到用户的充分肯定,其次,IP
SAN存储需要专门的驱动和设备,不过幸运的是一些传统的光纤适配器厂商都发布了iSCSI
HBA设备,同时Inter也推出了专用的IP存储适配器,而Microsoft、HP、Novell、SUN、AIX、Linux也具有了iSCSI
initiator软件,并且免费的提供给用户使用。最后,在安全方面,IP
SAN虽有一套规范的安全机制,但是尚未得到用户的认可。
这些问题和困扰是妨碍iSCSI发展的强大阻力,但是iSCSI的前途是光明的,相信在未来的存储世界里,IP
SAN绝对会拥有一席之地。
威尼斯www608cc,三、 iSCSI的组成 一个简单的iSCSI系统大致有以下部分组成:
? iSCSI initiator或者iSCSI HBA
? iSCSI target
? 以太网交换机
? 一台或者多台服务器
一个完整的iSCSI系统拓扑结构如图1所示:

在前面的文章中,介绍了如何搭建一个简单的iSCSI网络存储系统,作为iSCSI
initiator的客户端主机可以任意连接和使用iSCSI
target共享出来的所有磁盘和分区,而在很多时候,通过授权认证连接共享磁盘或分区是必须的,例如:只允许客户端主机A连接target共享出来的磁盘分区一,而客户端主机B只允许连接target共享出来的磁盘分区二等等,在这种情况下,就需要在iSCSI
target主机上进行授权设定了。

一、选用ISCSI原因
   
近期为了满足工作项目的设计需求,需要一个生产环境局域网内共享的大容量存储环境。考虑到价格因素,我选择采购了一台专用的网络存储服务器,硬盘全部为SATA
硬盘,通过自机网卡进行文件系统级别的共享。考虑到网络存储服务器只是用来存储别无他用,所以在系统的选择方面,我选择了linux
来提高机器的性能和效率,并使用ext2或ext3格式提高硬盘读写效率,当然这些效率的提高都是与Windows
操作系统进行对比的。对于共享,我选择安装使用常见的samba
来对硬盘某一目录进行NFS 方式文件共享,其余各服务器均使用windows 2003 x64
R2 操作系统,并统一映射网络路径//192.168.1.200/share 目录
为网络驱动器盘符z:到网络存储服务器共享出的目录中。
    在项目中期,随着功能测试和性能测试的开展,发现我们基于.net 开发的web
应用,并不能很好的直接使用网络驱动器,造成了不小的麻烦,而且我们所使用的oracle
数据库需要的很多的功能也不能正常使用这种网络共享。所以需要一个能让操作系统认为共享磁盘为本地硬盘的的一种解决方案才是最优的解决方案。

本篇文章将是『如何构建一个分布式爬虫』系列文章的最后一篇,拟从实战角度来介绍如何构建一个稳健的分布式微博爬虫。这里我没敢谈高效,抓过微博数据的同学应该都知道微博的反爬虫能力,也知道微博数据抓取的瓶颈在哪里。我在知乎上看过一些同学的说法,把微博的数据抓取难度简单化了,我只能说,那是你太naive,没深入了解和长期抓取而已。

威尼斯www608cc 1

威尼斯www608cc 2

iSCSI
在授权访问和安全管理方面有着不错优势,它能够使用以主机为基础,也就是以
IP地址为基础来设定允许或拒绝存取;也可以通过用户账号密码认证来完成允许或拒绝存取的设定。

二、了解ISCSI
    在所有剩余可选网络存储的解决方案中,除SAN
方式以外最好的方案不外乎ISCSI架构方式了,在ISCSI
架构中,各服务器作为客户端直接连接网络存储服务器的一个空间,并映射为本地的SCSI
逻辑硬盘,这样各个服务器对此硬盘操作的时候,就如同本地硬盘一样访问读写。并且由于ISCSI是块直接读写,理论上应该比其他共享方式在效率上要快很多。
    首先我们要了解iSCSI 架构中的角色与专词,iSCSI 的储存设备称为iSCSI
Target(或称iSCSI Target Device),例如iSCSI 磁盘阵列柜、iSCSI
磁带柜等,而iSCSI 卡称为iSCSI HBA(Host Bus Adapter),当然,iSCSI
允许使用一般Ethernet NIC
卡(网络卡,为了效率多半是GbE以上等级)与Ethernet
Switch(交换器),若使用一般GbE 卡,则还需要搭配软件才能让GbE
卡收发iSCSI 协议,此软件称为iSCSI Initiator,事实上iSCSI
HBA的角色也等同于iSCSI Initiator。
    简单来说在软件构成的ISCSI 方案中,ISCSI
Target可以被视作ISCSI服务器端,ISCSI Initiator
可以被视作客户端。在本次项目中,我决定由现有的网络存储服务器担任ISCSI
Target,其余服务器都安装iSCSI
Initiator来进行与存储的通信并映射网络存储服务器的指定空间为本地硬盘。
    不过,使用软件实现ISCSI必须多加权衡,由于它运用服务器的CPU
来进行iSCSI
协议的编解运算,会折损服务器的本务运算效能(即伺服应用服务的运算),建议在性能非常好的服务器上使用,且也要多斟酌效能冲击性,也不建议直接以服务器内唯一的GbE
网埠来传发iSCSI
协议,因为这将阻碍服务器原有对前端服务的能力(即Internet/LAN 与SAN
的传输交迭影响),所以多会额外加装第二张GbE
网卡,以另一专属区网(SAN)的作法来传输iSCSI。

本文将会以PC端微博进行讲解,因为移动端微博数据不如PC短全面,而且抓取和解析难度都会小一些。文章比较长,由于篇幅所限,文章并没有列出所有代码,只是讲了大致流程和思路。

 
这里将Target主机第三块硬盘(硬盘标识为/dev/sdc)作为iSCSI共享磁盘,硬盘大小为10G,分别共享给一台windows主机和一台Linux主机,基本结构如图1所示:

澳门威尼斯人网址, 图1
 

下面通过一个应用案例来讲述iSCSI授权获取磁盘资源的方法。

三、软件实现ISCSI 需要的工具和准备工作  
    软件实现ISCSI
要按操作系统分,主要推荐以下几种:(都是免费的,或者收费我还不知道!-_-!)


威尼斯www608cc 3

在此图中,iSCSI服务器用来安装iSCSI driver,也就是安装iSCSI
initiator,Storage
Router可以是以太网交换机或者路由器,iSCSI存储设备可以是iSCSI磁盘阵列,也可以是具有存储功能的PC服务器。下面详细介绍一下iSCSI
initiator与iSCSI target的含义:
四、iSCSI initiator iSCSI
initiator是一个安装在计算机上的一个软件或是一个硬件设备,它负责处理同iSCSI存储设备进行通信。
iSCSI服务器与iSCSI存储设备之间的连接方式有两种:第一种是基于软件的方式,也就是iSCSI
initiator,在iSCSI服务器上安装initiator后,Initiator软件可以将以太网卡虚拟为iSCSI卡,进而接受和发送iSCSI数据报文,从而实现主机和iSCSI存储设备之间的iSCSI协议和TCP/IP协议传输功能。这种方式只需以太网卡和以太网交换机,无需其它设备,因此成本是最低的,但是iSCSI包文和TCP/IP包文转换需要消耗iSCSI服务器的一部分cpu资源,只有在低I/O和低带宽性能要求的应用环境中才能使用这种方式。
第二种是硬件iSCSI HBA(Host Bus Adapter)卡方式,即为硬件iSCSI
initiator,这种方式需要购买iSCSI
HBA卡,然后安装在iSCSI服务器上,从而实现iSCSI服务器与交换机之间、iSCSI服务器与存储设备之间的高效数据传输。与第一种方式相比,硬件iSCSI
HBA卡方式不需要消耗iSCSI服务器的CPU资源,同时硬件设备是专用的,所以基于硬件的iSCSI
initiator可以提供更好的数据传输和存储性能。但是,iSCSI
HBA卡价格比较昂贵,因而,要在性能和成本之间进行权衡。
iSCSI
initiator软件一般都是免费的,CentOS和RHEL对iSCSI
Initiator支持非常不错,现在的Linux发行版本都默认已经自带了iSCSI
Initiator。

有一个PC构架的iSCSI
target服务器,共享的硬盘标识为/dev/sdc,大小10G,然后此硬盘划分了两个分区/dev/sdc1和/dev/sdc2,分别将/dev/sdc1共享给一个IP地址为192.168.12.136的windows客户端主机,将/dev/sdc2共享给一个IP地址为192.168.12.26的Linux客户端主机,iSCSI
target服务器的IP地址为192.168.12.246。接下来通过IP认证和用户密码认证两种方式来讲述如何实现这种需求。

    Target  Initiator
 Linux  iscsitarget-0.4.12-6.src.rpm  用linux 自带的就好了
 Windows  starwind  starport

要抓微博数据,第一步便是模拟登陆,因为很多信息(比如用户信息,用户主页微博数据翻页等各种翻页)都需要在登录状态下才能查看。关于模拟登陆进阶,我写过两篇文章,一篇是模拟登陆微博的,是从小白的角度写的。另外一篇是模拟登陆百度云的,是从有一定经验的熟手的角度写的。读了这两篇文章,并且根据我写的过程自己动手实现过的同学,应该对于模拟登陆PC端微博是没有太大难度的。那两篇文章没有讲如何处理验证码,这里我简单说一下,做爬虫的同学不要老想着用什么机器学习的方法去识别复杂验证码,真的难度非常大,这应该也不是一个爬虫工程师的工作重点,当然这只是我的个人建议。工程化的项目,我还是建议大家通过打码平台来解决验证码的问题。我在分布式微博爬虫中就是直接调用打码平台的接口来做的大规模微博账号的模拟登陆,效果还不错,而且打码成本很低。

图1
 

威尼斯www608cc 4

一、Initiator主机以IP认证方式获取iSCSI Target资源  此种方式配置非常简单,只需在iSCSI
target服务器上修改两个文件即可,首先在iscsitarget主目录/etc/iet目录下找到ietd.conf文件,然后添加如下内容:
Target iqn.2000-04.net.ixdba:sdc1
Lun 0 Path=/dev/sdc1,Type=fileio
Target iqn.2002-04.net.ixdba:sdc2
Lun 0 Path=/dev/sdc2,Type=fileio
在ietd.conf文件中,定义了两个Target,每个Target分别添加了对应的磁盘分区,接着修改/etc/iet/initiators.allow文件,这个文件是定义Initiator主机对target服务器的访问规则,作用类似与Linux操作系统中的/etc/hosts.allow文件。修改完成的initiators.allow文件内容如下:
iqn.2000-04.net.ixdba:sdc1 192.168.12.136
iqn.2002-04.net.ixdba:sdc2 192.168.12.26
修改完成,重启iscsi-target服务:
[root@iscsi-target iet]# service iscsi-target restart
Stopping iSCSI Target:                                     [  OK  ]
Starting iSCSI Target:                                     [  OK  ]
接着,在IP地址为192.168.12.26的Linux Initiator主机上执行如下操作:
[root@ Initiator iscsi]# /etc/init.d/iscsi restart
[root@ Initiator iscsi]#iscsiadm -m discovery -t sendtargets -p
192.168.12.246 
192.168.12.246:3260,1 iqn.2002-04.net.ixdba:sdc2
[root@ Initiator iscsi]#fdisk -l
Disk /dev/sda: 320.0 GB, 320072933376 bytes
255 heads, 63 sectors/track, 38913 cylinders
Units = cylinders of 16065 * 512 = 8225280 bytes

windows 下的Initiator 还可以使用微软自己的客户端:
   

如果操作系统为Windows VISTA 的话,那么恭喜你,系统已经自带ISCSI
Initiator 了。

说完模拟登陆(具体请参见我写的那两篇文章,篇幅所限,我就不copy过来了),我们现在正式进入微博的数据抓取。这里我会以微博用户信息抓取为例来进行分析和讲解。

下面开始详细介绍iSCSI网络存储的搭建过程。

   Device Boot      Start         End      Blocks   Id  System
/dev/sda1   *           1          13      104391   83  Linux
/dev/sda2              14       38913   312464250   8e  Linux LVM

    其实不管是由任意的操作系统做 Target 还是做Initiator
,由于有规范的ISCSI
协议的约束,都是可以相互进行通信的,所以可以任意自由组合选择。
    废话到此为止,赶快介绍一下Linux 下的 ISCSI Target的安装方法!

关于用户信息抓取,可能我们有两个目的。一个是我们只想抓一些指定用户,另外一个是我们想尽可能多的抓取更多数量的用户的信息。我的目的假定是第二种。那么我们该以什么样的策略来抓取,才能获得尽可能多的用户信息呢?如果我们初始用户选择有误,选了一些不活跃的用户,很可能会形成一个环,这样就抓不了太多的数据。这里有一个很简单的思路:我们把一些大V拿来做为种子用户,我们先抓他们的个人信息,然后再抓大V所关注的用户和粉丝,大V关注的用户肯定也是类似大V的用户,这样的话,就不容易形成环了。

一、安装iSCSI target软件 安装iscsitarget软件是在Target主机上进行的,即上面设定的192.168.12.246主机,这里我们选择的target软件是iscsitarget,读者可以从
[root@iscsi-target iscsi]#tar -xzvf iscsitarget-1.4.20.1.tar.gz
[root@iscsi-target iscsi]#cd iscsitarget-1.4.20.1
[root@iscsi-target iscsitarget-1.4.20.1]#make
[root@iscsi-target iscsitarget-1.4.20.1]#make install
 Iscsitarget安装完毕后,会创建/etc/iet/目录,此目录下有Iscsitarget的相关配置文件,接着就可以启动Iscsitarget服务了,启动或关闭Iscsitarget服务的命令如下:
[root@iscsi-target iscsi]# service iscsi-target       
Usage: /etc/init.d/iscsi-target
{start|stop|restart|condrestart|status}
如果要让iscsi-target服务开机自动运行,需执行如下操作:
[root@iscsi-target iscsi]#chkconfig –level 35 iscsi-target on
到此为止,iscsitarget安装完成。

Disk /dev/sdb: 5724 MB, 5724794880 bytes
177 heads, 62 sectors/track, 1018 cylinders
Units = cylinders of 10974 * 512 = 5618688 bytes

四、用Linux Red Hat Enterprise 4 + ISCSI Target 软件实现一个强大的ISCSI
Target 网络存储服务器。
    以下操作全部为root 权限下操作。
1.首先肯定是准备好Linux 系统环境。
 
    操作系统我选用的是Red Hat Enterprise Linux 4 (RHEL4)
,别的我也没有时间试,这个版本历经考验比较稳定。
   
安装的时候,如果系统只用来做存储服务器的话,选择基本的安装就好,不用安装杂七杂八的服务。
   
    安装完操作系统,最好配置该机上网,更新Kernel(当然需要有RedHat
Network 口令),使用以下命令:
    (当然如果你用的版本就是AS 4 的话,可以完全跳过这一步。)
    up2date kernel
    (更新完成后,要重新关闭并打开一个新的终端窗口。)
    然后更新Kernel 组件
    up2date kernel-devel openssl-devel gcc
rpm-build
   
2.  下载并安装iSCSI Enterprise Target (IET) RPM 包。
    我计划下载RPM 包以后
放在/usr/src/iscsitarget,然后再编译运行,当然你可以自己选择喜欢的安装位置。执行以下命令:

策略我们都清楚了。就该是分析和编码了。

二、配置一个简单的iSCSI target  iSCSI Enterprise
Target的主配置文件为/etc/iet/ietd.conf,此文件中的选项默认全部被注释掉了,有需要用到这些选项时,再将注释去掉即可。
 打开ietd.conf文件,首先找到类似如下行:
#Target iqn.2001-04.com.example:storage.disk2.sys1.xyz
此选项表示该iSCSI Target的命名,先将前面的“#”号去掉,
Target的命名在同一子网内应该是唯一的,标准命名方式为:
iqn.yyyy-mm.<reversed domain name>[:identifier]
其中:
? iqn:表示“iSCSI Qualified Name”,简称iqn。
? yyyy-mm:表示年份-月份。这里是2001-04。
? reversed domain name:表示倒过来的域名,这里是com.example。
? identifier:表示识别代码,这里是storage.disk2.sys1.xyz。
接下来,就是要设定 LUN(Logical Unit Number),找到类似如下行:
#Lun 0 Path=/dev/sdb,Type=fileio,ScsiId=xyz,ScsiSN=xyz
将前面的“#”号去掉,“Lun 0
Path=/dev/sdb”表示块设备号为0,映射的磁盘为/dev/sdb,“Type”值fileio是默认的,可以用于磁盘、file和LVM,这里设定的是“fileio”,主要用来对一个磁盘进行存储共享。读者可以根据自己情况将Path改为需要共享的存储分区的设备标识。这里假定共享的设备标识为/dev/sdb。
至此,一个简单的iSCSI Target已经配置完毕了,最后启动iscsi-target服务:
[root@iscsi-target iscsi]# service iscsi-target start

   Device Boot      Start         End      Blocks   Id  System
/dev/sdb1               1        1018     5585735   83  Linux
通过重启iscsi服务,重新执行Target发现,Linux系统已经识别了Target共享出来的磁盘分区,其中“/dev/sdb:
5724
MB”就是iSCSI共享磁盘,接下来就可以在linux上管理和使用这个共享磁盘了。
最后,登录windows系统,打开Microsoft iSCSI
Initiator,添加iSCSI共享磁盘即可,这个操作很简单,这里不在详述。

    建立安装位置目录
    mkdir /usr/src/iscsitarget
   
    进入目录
    cd /usr/src/iscsitarget

我们先来分析如何构造用户信息的URL。这里我以微博名为一起神吐槽的博主为例进行分析。做爬虫的话,一个很重要的意识就是爬虫能抓的数据都是人能看到的数据,反过来,人能在浏览器上看到的数据,爬虫几乎都能抓。这里用的是几乎,因为有的数据抓取难度特别。我们首先需要以正常人的流程看看怎么获取到用户的信息。我们先进入该博主的主页,如下图

三、在windows上配置iSCSI Initiator  下面的操作是在Initiator的windows主机上进行,即IP为192.168.12.136主机。
微软对iSCSI Initiator的支持相当完备,读者可以免费从微软网站获得iSCSI
Initiator软件,网址是
,本章实例下载的版本是Initiator-2.08-build3825-x86fre.exe,接下来开始说明如何让windows连接iSCSI
Target。
 安装完成iSCSI Initiator后,在桌面上会发现启动图标,启动Microsoft iSCSI
Initiator后,选择第二个分页标签“Discovery”,然后在“Target
Portals”部分点击“Add”按钮,跳出“Add Target Portal”窗口,如图2所示:
 

威尼斯www608cc 5

    下载安装包
    wget

    或
    wget

    
(当然也可以用浏览器访问
来获取其他版本或形式的安装包,保存在安装位置目录中)

种子用户主页

威尼斯www608cc 6

3.解压RPM源文件
    rpmbuild –rebuild
iscsitarget-0.4.12-6.src.rpm

点击查看更多,可以查看到该博主的具体信息

图2
 

4.build ISCSI target 和 ISCSI Kernel 源,并进行安装

种子博主具体信息

在此窗口下填写iSCSI Target的ip地址和端口,iSCSI
Target地址就是上面设定的Target主机的地址,iSCSI
Target的端口默认是3260,除非有特殊设定,填写完成,点击OK按钮。

    rpm -Uvh
/usr/src/redhat/RPMS/i386/iscsitarget-0.4.12-6.i386.rpm
/usr/src/redhat/RPMS/i386/iscsitarget-kernel-0.4.12-6_2.6.9_22.0.2.EL.i386.rpm

这里我们就看到了他的具体信息了。然后,我们看该页面的url构造

威尼斯www608cc 7

5.在chkconfig 中添加并启用 ISCSI target 服务。
    chkconfig –add iscsi-target
    chkconfig –level 2345 iscsi-target on
    或
    chkconfig iscsi-target on

http://weibo.com/p/1005051751195602/info?mod=pedit\_more

6.配置 ISCSI Target 服务
    ISCSI Target 服务的配置文件位于 /etc/ietd.conf 。可以使用vi
编辑器进行编辑,这里我准备将linux 本地的 sdb 物理盘共享出来用作 ISCSI
DISK.
   
    命令:
    vi /etc/ietd.conf
   
    内容大致如下(更改后):
    # iscsi target configuration

我直接copy的地址栏的url。这样做有啥不好的呢?对于老鸟来说,一下就看出来了,这样做的话,可能会导致信息不全,因为可能有些信息是动态加载的。所以,我们需要通过抓包来判断到底微博会通过该url返回所有信息,还是需要请求一些ajax
链接才会返回一些关键信息。这里我就重复一下我的观点:抓包很重要抓包很重要抓包很重要!重要的事情说三遍。关于抓包,我在模拟登陆微博和模拟登陆百度云都详细讲过了,这里我就不讲了。

    Target iqn.2008-03.com.digicola:storage.lun1
        IncomingUser iscsiuser pass123
        OutgoingUser
        Lun 0 Path=/dev/sdb,Type=fileio
        Alias iDISK0
        #MaxConnections  6

我们抓完包,发现并没有ajax请求。那么可以肯定请求前面的url,会返回所有信息。我们通过点击鼠标右键,查看网页源代码,然后ctrl+actrl+c将所有的页面源码保存到本地,这里我命名为personinfo.html。我们用浏览器打开该文件,发现我们需要的所有信息都在这段源码中,这个工作和抓包判断数据是否全面有些重复,但是在我看来是必不可少的,因为我们解析页面数据的时候还可以用到这个html文件,如果我们每次都通过网络请求去解析内容的话,那么可能账号没一会儿就会被封了(因为频繁访问微博信息),所以我们需要把要解析的文件保存到本地

    大致说明:
       IncomingUser 和 OutgoingUser  表示ISCSI
客户端的用户名和密码,用户名和密码都可以为空,默认为allow权限,密码最长可为12个字符。

从上面分析中我们可以得知

       Target iqn.2000-12.com.digicola:storage.lun1 表示该ISCSI Target
的命名,命名在同一子网内应该是唯一的,标准命名方式为:
       “Target “+ target名字 (格式如下: iqn.yyyy-mm.<reversed domain
name>[:identifier] )
     
        本次配置中 Type
的设定为”fileio”,我主要用来对一个磁盘进行存储共享。
        当然也可以针对需要设置为: “file” or “LVM” (稍微玩过linux
的人都能看明白吧!这里不用做过多介绍)。

http://weibo.com/p/1005051751195602/info?mod=pedit\_more

7.启动 ISCSI Target 进程,并检查ISCSI  Target 启动状态
    使用以下命令启动ISCSI 服务:
    service iscsi-target restart
   
    如果服务启动正常,为了确认建议还是到系统日志中检查一下:
    cat /var/log/messages

这个url就是获取用户数据的url。那么我们在只知道用户id的时候怎么构造它呢?我们可以多拿几个用户id来做测试,看构造是否有规律,比如我这里以用户名为网易云音乐的用户做分析,发现它的用户信息页面构造如下

   
当然你的配置出错,有可能在服务启动的时候显示服务启动正常,但是链接不上,这时你可以在系统日志中查看到具体的错误类型。

http://weibo.com/1721030997/about

    至此,一个标准的 ISCSI Target 服务就配置完毕了,你现在就可以使用
ISCSI Initiator 链接到这个服务器的 IP:3260 就可以链接上Target 了。
链接至Target
以后,就可以在磁盘管理器中找到你共享出的硬盘了,你可以随意对其格式化,分区做本地硬盘的任何操作。
    至于各系统版本 的ISCSI Initiator
的链接过程,我会另外建立一篇专门描述。

这个就和上面那个不同了。但是我们仔细观察,可以发现上面那个是个人用户,下面是企业微博用户。我们尝试一下把它们url格式都统一为第一种或者第二种的格式

http://weibo.com/1751195602/about

这样会出现404,那么统一成上面那种呢?

http://weibo.com/p/1005051721030997/info?mod=pedit\_more

这样子的话,它会被重定向到用户主页,而不是用户详细资料页。所以也就不对了。那么该以什么依据判断何时用第一种url格式,何时用第二种url格式呢?我们多翻几个用户,会发现除了100505之外,还有100305100206等前缀,那么我猜想这个应该可以区分不同用户。这个前缀在哪里可以得到呢?我们打开我们刚保存的页面源码,搜索100505,可以发现

domain

微博应该是根据这个来区分不同用户类型的。这里大家可以自己也可以试试,看不同用户的domain是否不同。为了数据能全面,我也是做了大量测试,发现个人用户的domain是1005051,作家是100305,其他基本都是认证的企业号。前两个个人信息的url构造就是

http://weibo.com/p/domain+uid/info?mod=pedit\_more

后者的是

http://weibo.com/uid/about

弄清楚了个人信息url的构造方式,但是还有一个问题。我们已知只有uid啊,没有domain啊。如果是企业号,我们通过domain=100505会被重定向到主页,如果是作家等(domain=100305或者100306),也会被重定向主页。我们在主页把domain提取出来,再请求一次,不就能拿到用户详细信息了吗?

关于如何构造获取用户信息的url的相关分析就到这里了。因为我们是在登录的情况下进行数据抓取的,可能在抓取的时候,某个账号突然就被封了,或者由于网络原因,某次请求失败了,该如何处理?对于前者,我们需要判断每次请求返回的内容是否符合预期,也就是看response
url是否正常,看response
content是否是404或者让你验证手机号等,对于后者,我们可以做一个简单的重试策略,大概代码如下

@timeout_decorator
def get_page(url, user_verify=True, need_login=True):
    """
    :param url: 待抓取url
    :param user_verify: 是否为可能出现验证码的页面(ajax连接不会出现验证码,如果是请求微博或者用户信息可能出现验证码),否为抓取转发的ajax连接
    :param need_login: 抓取页面是否需要登录,这样做可以减小一些账号的压力
    :return: 返回请求的数据,如果出现404或者403,或者是别的异常,都返回空字符串
    """
    crawler.info('本次抓取的url为{url}'.format(url=url))
    count = 0

    while count < max_retries:

        if need_login:
            # 每次重试的时候都换cookies,并且和上次不同,如果只有一个账号,那么就允许相同
            name_cookies = Cookies.fetch_cookies()

            if name_cookies is None:
                crawler.warning('cookie池中不存在cookie,正在检查是否有可用账号')
                rs = get_login_info()

                # 选择状态正常的账号进行登录,账号都不可用就停掉celery worker
                if len(rs) == 0:
                    crawler.error('账号均不可用,请检查账号健康状况')
                    # 杀死所有关于celery的进程
                    if 'win32' in sys.platform:
                        os.popen('taskkill /F /IM "celery*"')
                    else:
                        os.popen('pkill -f "celery"')
                else:
                    crawler.info('重新获取cookie中...')
                    login.excute_login_task()
                    time.sleep(10)

        try:
            if need_login:
                resp = requests.get(url, headers=headers, cookies=name_cookies[1], timeout=time_out, verify=False)

                if "$CONFIG['islogin'] = '0'" in resp.text:
                    crawler.warning('账号{}出现异常'.format(name_cookies[0]))
                    freeze_account(name_cookies[0], 0)
                    Cookies.delete_cookies(name_cookies[0])
                    continue
            else:
                resp = requests.get(url, headers=headers, timeout=time_out, verify=False)

            page = resp.text
            if page:
                page = page.encode('utf-8', 'ignore').decode('utf-8')
            else:
                continue

            # 每次抓取过后程序sleep的时间,降低封号危险
            time.sleep(interal)

            if user_verify:
                if 'unfreeze' in resp.url or 'accessdeny' in resp.url or 'userblock' in resp.url or is_403(page):
                    crawler.warning('账号{}已经被冻结'.format(name_cookies[0]))
                    freeze_account(name_cookies[0], 0)
                    Cookies.delete_cookies(name_cookies[0])
                    count += 1
                    continue

                if 'verifybmobile' in resp.url:
                    crawler.warning('账号{}功能被锁定,需要手机解锁'.format(name_cookies[0]))

                    freeze_account(name_cookies[0], -1)
                    Cookies.delete_cookies(name_cookies[0])
                    continue

                if not is_complete(page):
                    count += 1
                    continue

                if is_404(page):
                    crawler.warning('url为{url}的连接不存在'.format(url=url))
                    return ''

        except (requests.exceptions.ReadTimeout, requests.exceptions.ConnectionError, AttributeError) as e:
            crawler.warning('抓取{}出现异常,具体信息是{}'.format(url, e))
            count += 1
            time.sleep(excp_interal)

        else:
            Urls.store_crawl_url(url, 1)
            return page

    crawler.warning('抓取{}已达到最大重试次数,请在redis的失败队列中查看该url并检查原因'.format(url))
    Urls.store_crawl_url(url, 0)
    return ''

这里大家把上述代码当一段伪代码读就行了,主要看看如何处理抓取时候的异常。因为如果贴整个用户抓取的代码,不是很现实,代码量有点大。


下面讲页面解析的分析。有一些做PC端微博信息抓取的同学,可能曾经遇到过这么个问题:保存到本地的html文件打开都能看到所有信息啊,为啥在页面源码中找不到呢?因为PC端微博页面的关键信息都是像下图这样,被FM.view()包裹起来的,里面的数据可能被json
encode
过。

标签

那么这么多的FM.view(),我们怎么知道该提取哪个呢?这里有一个小技巧,由于只有中文会被编码,英文还是原来的样子,所以我们可以看哪段script中包含了渲染后的页面中的字符,那么那段应该就可能包含所有页面信息。我们这里以顶部的头像为例,如图

根据唯一性判断

我们在页面源码中搜索,只发现一个script中有该字符串,那么就是那段script是页面相关信息。我们可以通过正则表达式把该script提取出来,然后把其中的html也提取出来,再保存到本地,看看信息是否全面。这里我就不截图了。感觉还有很多要写的,不然篇幅太长了。

另外,对于具体页面的解析,我也不做太多的介绍了。太细的东西还是建议读读源码。我只讲一下,我觉得的一种处理异常的比较优雅的方式。微博爬虫的话,主要是页面样式太多,如果你打算包含所有不同的用户的模版,那么我觉得几乎不可能,不同用户模版,用到的解析规则就不一样。那么出现解析异常如何处理?尤其是你没有catch到的异常。很可能因为这个问题,程序就崩掉。其实对于Python这门语言来说,我们可以通过
装饰器 来捕捉我们没有考虑到的异常,比如我这个装饰器

def parse_decorator(return_type):
    """
    :param return_type: 用于捕捉页面解析的异常, 0表示返回数字0, 1表示返回空字符串, 2表示返回[],3表示返回False, 4表示返回{}, 5返回None
    :return: 0,'',[],False,{},None
    """
    def page_parse(func):
        @wraps(func)
        def handle_error(*keys):
            try:
                return func(*keys)
            except Exception as e:
                parser.error(e)

                if return_type == 5:
                    return None
                elif return_type == 4:
                    return {}
                elif return_type == 3:
                    return False
                elif return_type == 2:
                    return []
                elif return_type == 1:
                    return ''
                else:
                    return 0

        return handle_error

    return page_parse

上面的代码就是处理解析页面发生异常的情况,我们只能在数据的准确性、全面性和程序的健壮性之间做一些取舍。用装饰器的话,程序中不用写太多的
try语句,代码重复率也会减少很多。

页面的解析由于篇幅所限,我就讲到这里了。没有涉及太具体的解析,其中一个还有一个比较难的点,就是数据的全面性,读者可以去多观察几个微博用户的个人信息,就会发现有的个人信息,有的用户有填写,有的并没有。解析的时候要考虑完的话,建议从自己的微博的个人信息入手,看到底有哪些可以填。这样可以保证几乎不会漏掉一些重要的信息。


最后,我再切合本文的标题,讲如何搭建一个分布式的微博爬虫。开发过程中,我们可以先就做单机单线程的爬虫,然后再改成使用celery的方式。这里这样做是为了方便开发和测试,因为你单机搭起来并且跑得通了,那么分布式的话,就很容易改了,因为celery的API使用本来就很简洁。

我们抓取的是用户信息和他的关注和粉丝uid。用户信息的话,我们一个请求大概能抓取一个用户的信息,而粉丝和关注我们一个请求可以抓取18个左右(因为这个抓的是列表),显然可以发现用户信息应该多占一些请求的资源。这时候就该介绍理论篇没有介绍的关于celery的一个高级特性了,它叫做任务路由。直白点说,它可以规定哪个分布式节点能做哪些任务,不能做哪些任务。它的存在可以让资源分配更加合理,分布式微博爬虫项目初期,就没有使用任务路由,然后抓了十多万条关注和粉丝,发现用户信息只抓了几万条,这就是资源分配得不合理。那么如何进行任务路由呢?

# coding:utf-8
import os
from datetime import timedelta
from celery import Celery
from kombu import Exchange, Queue
from config.conf import get_broker_or_backend
from celery import platforms

# 允许celery以root身份启动
platforms.C_FORCE_ROOT = True

worker_log_path = os.path.join(os.path.dirname(os.path.dirname(__file__))+'/logs', 'celery.log')
beat_log_path = os.path.join(os.path.dirname(os.path.dirname(__file__))+'/logs', 'beat.log')

tasks = ['tasks.login', 'tasks.user']

# include的作用就是注册服务化函数
app = Celery('weibo_task', include=tasks, broker=get_broker_or_backend(1), backend=get_broker_or_backend(2))

app.conf.update(
    CELERY_TIMEZONE='Asia/Shanghai',
    CELERY_ENABLE_UTC=True,
    CELERYD_LOG_FILE=worker_log_path,
    CELERYBEAT_LOG_FILE=beat_log_path,
    CELERY_ACCEPT_CONTENT=['json'],
    CELERY_TASK_SERIALIZER='json',
    CELERY_RESULT_SERIALIZER='json',
    CELERY_QUEUES=(
        Queue('login_queue', exchange=Exchange('login', type='direct'), routing_key='for_login'),
        Queue('user_crawler', exchange=Exchange('user_info', type='direct'), routing_key='for_user_info'),
        Queue('fans_followers', exchange=Exchange('fans_followers', type='direct'), routing_key='for_fans_followers'),
)

上述代码我指定了有login_queueuser_crawlerfans_followers三个任务队列。它们分别的作用是登录、用户信息抓取、粉丝和关注抓取。现在假设我有三台爬虫服务器A、B和C。我想让我所有的账号登录任务分散到三台服务器、让用户抓取在A和B上执行,让粉丝和关注抓取在C上执行,那么启动A、B、C三个服务器的celery
worker的命令就分别是

celery -A tasks.workers -Q login_queue,user_crawler worker -l info
-c 1 #
A服务器和B服务器启动worker的命令,它们只会执行登录和用户信息抓取任务

celery -A tasks.workers -Q login_queue,fans_followers worker -l info
-c 1 # C服务器启动worker的命令,它只会执行登录、粉丝和关注抓取任务

然后我们通过命令行或者代码(如下)就能发送所有任务给各个节点执行了

# coding:utf-8
from tasks.workers import app
from page_get import user as user_get
from db.seed_ids import get_seed_ids, get_seed_by_id, insert_seeds, set_seed_other_crawled


@app.task(ignore_result=True)
def crawl_follower_fans(uid):
    seed = get_seed_by_id(uid)
    if seed.other_crawled == 0:
        rs = user_get.get_fans_or_followers_ids(uid, 1)
        rs.extend(user_get.get_fans_or_followers_ids(uid, 2))
        datas = set(rs)
        # 重复数据跳过插入
        if datas:
            insert_seeds(datas)
        set_seed_other_crawled(uid)


@app.task(ignore_result=True)
def crawl_person_infos(uid):
    """
    根据用户id来爬取用户相关资料和用户的关注数和粉丝数(由于微博服务端限制,默认爬取前五页,企业号的关注和粉丝也不能查看)
    :param uid: 用户id
    :return: 
    """
    if not uid:
        return

    # 由于与别的任务共享数据表,所以需要先判断数据库是否有该用户信息,再进行抓取
    user = user_get.get_profile(uid)
    # 不抓取企业号
    if user.verify_type == 2:
        set_seed_other_crawled(uid)
        return
    app.send_task('tasks.user.crawl_follower_fans', args=(uid,), queue='fans_followers',
                  routing_key='for_fans_followers')


@app.task(ignore_result=True)
def excute_user_task():
    seeds = get_seed_ids()
    if seeds:
        for seed in seeds:
            # 在send_task的时候指定任务队列
            app.send_task('tasks.user.crawl_person_infos', args=(seed.uid,), queue='user_crawler',
                          routing_key='for_user_info')

这里我们是通过
queue='user_crawler',routing_key='for_user_info'来将任务和worker进行关联的。

关于celery任务路由的更详细的资料请阅读官方文档。


到这里,基本把微博信息抓取的过程和分布式进行抓取的过程都讲完了,具体实现分布式的方法,可以读读基础篇。由于代码量比较大,我并没有贴上完整的代码,只讲了要点。分析过程是讲的抓取过程的分析和页面解析的分析,并在最后,结合分布式,讲了一下使用任务队列来让分布式爬虫更加灵活和可扩展。

如果有同学想跟着做一遍,可能需要参考分布式微博爬虫的源码,自己动手实现一下,或者跑一下,印象可能会更加深刻。

相关文章