这次的项目 和文件都放到了 github 上 https://github.com/poiu1235/weibo-catch:
有兴趣的可以follow一下,或者点个赞咯
我这里采用的深度挖掘的方式:没有设定爬取的边界(这个以后是要考虑的)
大致的思路是,用自己的 账号登陆后,获取自己的微博列表和朋友列表。
然后根据朋友列表然后在爬取对方的微博列表和朋友列表。这样不断的深度挖掘和遍历的过程
过程中我采用了mysql 数据库进行存储,后面会加入mongodb 数据库进行存储。
先补充一点linux的知识:系统里那些文件夹都按照什么功能分类的:
/bin
bin是binary的缩写。这个目录沿袭了UNIX系统的结构,存放着使用者最经常使用的命令。例如cp、ls、cat,等等。
/boot
这里存放的是启动Linux时使用的一些核心文件。
/dev
dev是device(设备)的缩写。这个目录下是所有Linux的外部设备,其功能类似DOS下的.sys和Win下的.vxd。在Linux中设备和文件是用同种方法访问的。例如:/dev/hda代表第一个物理IDE硬盘。
/etc
这个目录用来存放系统管理所需要的配置文件和子目录。
/home
用户的主目录,比如说有个用户叫wang,那他的主目录就是/home/wang也可以用~wang表示。
/lib
这个目录里存放着系统最基本的动态链接共享库,其作用类似于Windows里的.dll文件。几乎所有的应用程序都须要用到这些共享库。
/lost+found
这个目录平时是空的,当系统不正常关机后,这里就成了一些无家可归的文件的避难所。对了,有点类似于DOS下的.chk文件。
/mnt
这个目录是空的,系统提供这个目录是让用户临时挂载别的文件系统。
/proc
这个目录是一个虚拟的目录,它是系统内存的映射,我们可以通过直接访问这个目录来获取系统信息。也就是说,这个目录的内容不在硬盘上而是在内存里。
/root
系统管理员(也叫超级用户)的主目录。作为系统的拥有者,总要有些特权啊!比如单独拥有一个目录。
/sbin
s就是Super User的意思,也就是说这里存放的是系统管理员使用的管理程序。
/tmp
这个目录不用说,一定是用来存放一些临时文件的地方了。
/usr
这是最庞大的目录,我们要用到的应用程序和文件几乎都存放在这个目录下。其中包含以下子目录;
/usr/X11R6
存放X-Window的目录;
/usr/bin
存放着许多应用程序;
/usr/sbin
给超级用户使用的一些管理程序就放在这里;
/usr/doc
这是Linux文档的大本营;
/usr/include
Linux下开发和编译应用程序需要的头文件,在这里查找;
/usr/lib
存放一些常用的动态链接共享库和静态档案库;
/usr/local
这是提供给一般用户的/usr目录,在这里安装软件最适合;
/usr/man
man在Linux中是帮助的同义词,这里就是帮助文档的存放目录;
/usr/src
Linux开放的源代码就存在这个目录,爱好者们别放过哦!
/var
这个目录中存放着那些不断在扩充着的东西,为了保持/usr的相对稳定,那些经常被修改的目录可以放在这个目录下,实际上许多系统管理员都是这样干的。顺带说一下系统的日志文件就在/var/log目录中。
爬取微博规则
整个过程我想要爬取两个方面的内容。
一个是微博内容
另一个是朋友(他关注的人,主动,兴趣强烈)关系而不是(关注他的人,粉丝,被动,干扰太多)
爬取下来的话我也打算存在两个地方,一个是mysql 关系数据库里,另一个是以文本文件的形式存到系统
mysql 用的是之前hadoop 系统 里namenode 节点 里的mysql ,ip是192.168.1.113
因为默认mysql 安装时,是绑定本地ip的,就是只限定本地访问,单机访问
所以要更改配置
sudo nano /etc/mysql/my.cnf找到bind-address = 127.0.0.1
注释掉这行,如:#bind-address = 127.0.0.1
允许任意IP访问;
这样注释以后,本地访问就不能省略了 必须用-h 命令 指定主机地址
重启 MySQL:sudo /etc/init.d/mysql restart
授权用户能进行远程连接
grant all privileges on *.* to root@"%" identified by "password" with grant option;
flush privileges;
第一行命令解释如下,*.*:第一个*代表数据库名;第二个*代表表名。这里的意思是所有数据库里的所有表都授权给用户。root:授予root账号。“%”:表示授权的用户IP可以指定,这里代表任意的IP地址都能访问MySQL数据库。“password”:分配账号对应的密码,这里密码自己替换成你的mysql root帐号密码。
第二行命令是刷新权限信息,也即是让我们所作的设置马上生效。
登陆mysql 用 mysql -uroot -p
如果是登陆其他机器上的mysql 用 mysql -uroot -h192.168.1.113 -p
#查看编码方式
show variables like "character%";
#修改编码方式
sudo service mysql stop
sudo nano /etc/mysql/my.cnf
#在文件内的[mysqld]下增加如下两行设置:
character_set_server=utf8
init_connect='SET NAMES utf8'
sudo service mysql start
数据库字符集和 服务器字符集就会变成utf-8 了,就可以写中文了
这里我在ubuntu上安装的mysql 辅助工具是workbench 还有heidisql 网上不推荐navicat 虽然我在windows 下用navicat 最顺手
默认的mysql目录下data里面的'mysql'这个schema没有在workbench里面看到吧?
点击菜单-Edit->Preferences里面的SQL Editor,然后把"show Data Dicrionaries and Internal Schemas"前面的复选框给勾上,
再回过去刷新或者重新连接,就会出现了
接下来就是一些建库,建表的工作了
execute中要使用对等格式 如 name(char), age(int),此时语句为: cur.execute("insert into db.table(name,age) values(%s,%d)" % (v[name], v[age]) ,v[name]为字符,v[age]为int
executemany中不需要强制格式,同上表语句为:
values=[("zhan",23),("li",33)]
executemany("insert into db.tables(name,age) values(%s,%s)",values)
具体原因我也不清楚,只能说是走过的坑吧,executemany一般只用%s即可,用其它的可能会报错。插入后的值是正常的(貌似是自动匹配列属性?),有大侠知道的也望指教
注意: cursor.execute()可以接受一个参数,也可以接受两个参数:
(1) cursor.execute("insert into resource(cid,name) values(%s, %s)" , (12,name) );
这种格式是接受两个参数,MySQLdb会自动替你对字符串进行转义和加引号,不必再自己进行转义,执行完 此语句之后,resource表中多了一条记录: 12 \
(2) cursor.execute("insert into resource(cid,name) values(%s, %s)" % (12,name) );
这种格式是利用python的字符串格式化自己生成一个query,也就是传给execute一个参数,此时必须自己对 字符串转义和增加引号,即上边的语句是错误的,应该修改为:
name = MySQLdb.escape_string(name);
cursor.execute("insert into resource(cid,name) values(%s, '%s')" % (12,name) );
这样插入的记录才和(1)一样:12 \
create database weibocatch;
use weibocatch;
/*汉字和一个字母都是一个字符*/
create table w_user(
wid char(10) primary key,
wname varchar(100) not null,
recon tinyint(1), #0表示未认证,1表示已认证
color tinyint(1) default null,#0表示黄v(个人),1表示蓝v(企业)
flag tinyint(2) default 0, #0表示未被爬取,1表示爬取成功,2表示爬取失败
inserttime timestamp default NOW()
);
create table w_error(
wid char(10),
exception varchar(2000)
);
create table w_relation(
wid char(10),
wfriendid char(10)
);
create table w_conn(
weiboid char(32),
tag tinyint(1),/*判断是转发的微博还是原创的微博,1是转发,0是原创*/
atid char(10),
atname varchar(100),
primary key (weiboid,tag)
);
/*Date或DateTime类型是不能使用函数作为默认值的,所以改用timestamp类型*/
create table w_content(
weiboid char(32) primary key,
wid char(10),
content varchar(1000),
url varchar(1000),
map varchar(200),
label varchar(100),
intime varchar(100),
picid int,
wfrom varchar(100),
inserttime timestamp default NOW()
);
/*Date或DateTime类型是不能使用函数作为默认值的,所以改用timestamp类型*/
create table w_transfer(
weiboid char(32) primary key,
wid char(10),
transferid char(10),
content varchar(1000),
remark varchar(1000),
label varchar(100),
url varchar(1000),
intime varchar(100),
picid int,
wfrom varchar(100),
inserttime timestamp default NOW()
);
create table pic_reg(
id int primary key auto_increment,
path varchar(3000),
label1 varchar(100),
label2 varchar(100),
label3 varchar(100),
title varchar(9000)
)auto_increment=10000;
INSERT INTO weibocatch.w_user
(wid,wname,recon,color,flag)
VALUES
('2468833122','poiu1235',0,'',0);
INSERT INTO weibocatch.w_user
(wid,wname,recon,color,flag)
VALUES
('2430104687','江森开根号',0,0,0);
SHOW GLOBAL VARIABLES LIKE 'auto_incre%'; -- 全局变量
select * from weibocatch.w_relation;
select NOW();
delete from weibocatch.pic_reg;
delete from weibocatch.w_transfer;
alter table weibocatch.pic_reg AUTO_INCREMENT=10000;
select * from weibocatch.w_user;
update weibocatch.w_user set flag=0 where wid ='2430104687'
insert 和 update 操作完成后一定要 self.conn.commit(),不然不会改变数据库的
try:
xxxx;
except Exception as err:
self.conn.rollback()
print err
finally:
#关闭连接,释放资源
cursor.close();
self.conn.close()
if(wconn.tag.strip()):#判断字符串不为空
把准备数据库连接的过程写在一个函数里,这样每次临时要用在临时去调用
self.inition()#因为mysql 连接很容易超时就断开了,默认10s,所以索性每次都用的时候在申明
#插入一条记录 全部用%s作占位符号,即使是数字也不要用%d
#因为这个插件程序会帮你转换的,字符串会自动给你加'',如果数字不会加
#使用这函数向一个给定Connection对象返回的值是该Connection对象产生
#对影响AUTO_INCREMENT列的最新语句第一个AUTO_INCREMENT值的。
#这个值不能被其它Connection对象的影响,即它们产生它们自己的AUTO_INCREMENT值。
#第二、LAST_INSERT_ID 是与table无关的,
#如果向表a插入数据后,再向表b插入数据,LAST_INSERT_ID返回表b中的Id值。
cursor.execute("SELECT LAST_INSERT_ID()")
#"只获取一条记录:"
result = cursor.fetchone();#这个返回的是一个tuple,一个元素的元组后面都会跟一个”,“。没实际意义
#表示从第二个取到倒数第二个,去掉模式串中两头的单引号
loginweb=loginweb[1:-1]
#reload(sys)
#sys.setdefaultencoding( "utf-8" )#这个是因为插件报错,但是程序正常。
#str1.decode('gb2312'),表示将gb2312编码的字符串str1转换成unicode编码
#str2.encode('gb2312'),表示将unicode编码的字符串str2转换成gb2312编码。
#设置读取目录是当前目录
#但是虽然存文件方便了,但是由于当前路径变化导致不能正确读取当前cookies文件路径
#os.chdir("/home/luis/workspace/weibo-catch/picture/")
#os.getcwd()<pre name="code" class="python">#所以直接写完整路径会省去很多麻烦
ppth="/home/luis/workspace/weibo-catch/picture/"
data = urllib.urlopen(picurl).read()
f = open(name,'wb',8192)#设置文件缓冲区为8M大小,有的图片较大怕一次存不完
f.write(data)
f.close#close 方法相当于把缓冲区flush后再关闭的。
#因为f.read()方法读到的是str类型,默认是utf8, 可以直接用str类型进行查找,不用进行转码decode操作
if(countt==10):
subprocess.call("pause",shell=True)#让程序暂停替换了os.system('pause')方法 或者用if(raw_input()): pass
#注意每个beautifulsoup对象截取出来的对象都是”特定的标签“对象,可以直接进行beautifulsoup相关操作
#而不是字符串类型,如果要进行一些字符串操作,必须要转成str() 类型。
#Beautiful Soup用了 编码自动检测 子库来识别当前文档编码并转换成Unicode编码
#通过Beautiful Soup输出文档时,不管输入文档是什么编码方式,输出编码均为UTF-8编码
#但是像index 和 单纯的 find 方法会转成unicode编码方式再来操作,执行完后就要转码成utf8操作。
#即先转码再转回来(先decode(‘utf8’)再encode(‘utf8’))
time.sleep(2)#每次完成一个任务就暂停2秒
#一般的字符串操作有rfind 从后前前找,beautifulsoup没有
#不可以用remove 方法,包括del pop这些方法只有list有
#remove 是按照关键字删除,但是del 和pop 是按照索引删除
#表示去掉字串两头的空格,s.strip(rm)删除s字符串中开头、结尾处,位于rm序列里的字符,
#当rm为空时,默认删除空白符(包括'\n', '\r', '\t', ' ')
#get_text() 方法可以拿到全部内容,而content数组拿到的是被内层标签分段内容块。
transfercon=contentcut2.get_text()
s="原图"
sx=s.decode('utf-8')#千万要注意不能把两句写在一起,动态编译不会识别的
picurl=contentcut2.parent.next_sibling.find('a',text=sx)["href"]
#补充一点,双引号会对内容进行转义,单引号则不会
#为什么可以不用初始化对象就可以直接通过类名调用方法,当然是不可以的,运行的时候就会报错
#(这个错误很隐蔽,在编码过程中并不会报错,因为python是解释执行的
#在运行过程中就会找不到这个方法。
#解决方式,要么实例化对象后再调用方法,要么用“类方法”即在类名上加上@classmethod 修饰
#并且要把方法的第一个默认参数写成cls)这里我采用第二种方法,在类名上加修饰符
#类方法,用classmethod来进行修饰
#类方法的隐含调用参数是类,而类实例方法的隐含调用参数是类的实例,静态方法没有隐含调用参数
@classmethod
def findweibo(cls,sweb):
#注意:join()方法的位置是在for循环外的,也就是说必须等待for循环里的两个进程都结束后,才去执行主进程。
#或者结合flag 和while 来判断 程程是否还有效
#is_alive(): Return whether the thread is alive.
##findAll 拿到的是一系列标签的list
resp1 = soup.find('input', attrs = {'name':'vk'})['value']
#中文正则表达式
#前面带一个u 不是 r 并且 中文字符的编码范围是[\u4E00-\u9FA5]
reg=u"<a href=('http://login.weibo.cn/[^\u4E00-\u9FA5]*?)[>]+?[\u4E00-\u9FA5]{2}</a>"
反正所有项目文件都在github 上 https://github.com/poiu1235/weibo-catch: