兄弟!
是不是还单身?
想不想找女朋友?
想不想拉着她在西安的街头走一走,呜哦呜哦?
直到所有的灯都熄灭了,也不停留?
你会说,当然想,可是我在西电啊!
人家的校缘,那可都是“爱”!
我们的校媛,呸,猿,都是“唉”啊!
还好我们有睿思,有论坛,有相亲帖!
但是!
刚看上一个漂亮的小姐姐,想加微信撩,人家就结帖了,结帖了!
嗯?不知道结帖啥意思,看图!
对!就是这样!
人家已经被身披金甲圣衣,脚踏七彩祥云的盖世英雄给追到手了!
那你的余生是不是就只能找个男的凑合过日子了?
不!
身为肩负拯救世界重任的好心人,也就是我,不会让这种事发生的!
喏!
现在你可以在微信端实时接收最新的帖子啦!
她只要一在缘聚睿思版块发出求偶信号,你就可以在第!一!时!间!腆着个大脸过去撩了!
效果如下:
sorry,放错图了,是下面这张!
只要经过一些简单的配置,就可以享受这种黄金VIP级的待遇了!
没办法,谁让我人好,下面我就开始整了!
参考文档与博客
[1] BeautifulSoup中文文档
[2] itchat应用教程
[3] 校招提醒机器人-肖洒
环境部署
Anaconda是一个开源的python发行版本,个人习惯用python3,所以用的是Anaconda3。下载并安装好Anaconda3之后,python3、python3常用的依赖包、开发环境就都有了,开发环境习惯用jupyter notebook,当然其他的也可以。
另外还要用到两个包,BeautifulSoup和itchat,分别用于页面解析、与微信端交互,用pip安装,方法如下:
pip3 install beautifulsoup4
pip3 install itchat
解析相亲帖
某位女嘉宾的帖子是这样的
既有文本,又有照片,所以,按照套路,先导入所有要用到的包
from bs4 import BeautifulSoup#解析页面,第三方包
import urllib.request#请求网页
import itchat#用于微信端交互,第三方包
import time#定时器,python自带
import os#用于创建文件夹,python自带
import shutil#用于递归删除文件夹,python自带
解析页面,这里就直接引用大神校招提醒机器人-肖洒的代码了
def getPageContent(url):
#伪装浏览器
headers = {
#浏览器信息,我用的是chrome
'User-Agent' : 'Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.181 Safari/537.36',
#因为睿思论坛要先登录才能访问相亲帖,故要加入cookie,每个人的cookie都不同
'Cookie' : 'Q8qA_2132_saltkey=Qdmt1tYy; Q8qA_2132_lastvisit=1529493180; Q8qA_2132_ulastactivity=4a18XdrzuW602gpzpKQEU5prg6bxSTeIM7hEYMDp7k6uJ%2BnogwMZ; Q8qA_2132_lastcheckfeed=304070%7C1529496798; Q8qA_2132_myrepeat_rr=R0; Q8qA_2132_auth=770aHvS7Tt2zInpFLb4FtNeIrYgHBY5rjKxZPFoXgWGhYTec%2BPuWoM19uou%2BdGLELy%2BnLJKub8Fg8Opp%2FJnNnHmM64E; Q8qA_2132_home_diymode=1; Q8qA_2132_nofavfid=1; Q8qA_2132_smile=4D1; Q8qA_2132_visitedfid=217D13D110; Q8qA_2132_st_t=304070%7C1529503019%7Cd72f8f898362e189f2b2780ed6e60c93; Q8qA_2132_forum_lastvisit=D_106_1529500683D_110_1529500694D_217_1529503019; Q8qA_2132_st_p=304070%7C1529503185%7C00734d0bcbe0384484f7216ada21fa02; Q8qA_2132_viewid=tid_945811; Q8qA_2132_sid=Pb9XCX; Q8qA_2132_lip=10.170.41.52%2C1529503691; Q8qA_2132_lastact=1529503776%09misc.php%09patch'
}
#请求页面
req = urllib.request.Request(url=url,headers=headers)
try:
res = urllib.request.urlopen(req)
except urllib.error.URLError as e:
return e
page_content = res.read()
#用lxml方式解析网页
page_content=BeautifulSoup(page_content,"lxml")
return page_content
先分析一下这个网页,既有文本,又有图片,先分析文本怎么爬。
定位到文本对应的网页元素,发现不同的字体对应不同的标签,但所有文本都在td标签下,即上图中箭头所指。只要想办法定位到相应的td标签下,用get_text()就可以得到所有文本了。但直接使用get_text()方法还会包含一些“最后发帖由谁在什么时间完成”之类的杂七杂八的文字,所以在代码中要对这些无关文字再删除。
对于图片,我们先定位图片的url,它长这样
./data/attachment/forum/201805/15/212638i0ktdwi1c9298wix.jpg
没有以http打头,一看就是相对路径,但相对路径浏览器不能识别哇,怎么得到绝对路径?
考虑到当前的url,也就是地址栏中的地址,它是这样什儿的:
http://rs.xidian.edu.cn/forum.php?mod=viewthread&tid=937063&extra=page%3D1%26filter%3Dlastpost%26orderby%3Dlastpost
而./data又表示先跳到上一级再定位到data目录,所以图片在服务器中完整的绝对路径应该是把http://rs.xidian.edu.cn/后面的内容替换成data/attachment/forum/201805/15/212638i0ktdwi1c9298wix.jpg,即
http://rs.xidian.edu.cn/data/attachment/forum/201805/15/212638i0ktdwi1c9298wix.jpg
写一个函数来实现这种替换功能
def urlJoin(urlCur, urlImg):
#urlCur = 'http://rs.xidian.edu.cn/forum.php?mod=viewthread&tid=947526&extra=page%3D1%26filter%3Dlastpost%26orderby%3Dlastpost&page=1'
#urlImg = './data/attachment/forum/201806/20/235708qtq25ommzqqruoa9.jpg'
a = urlCur.split('/')
b = urlImg.split('/')
url = ''
for i in a[:-1]:
url += i + '/'
for i in b[1:]:
url += i + '/'
return url[:-1]
现在,文本的内容和图片的地址我们都知道怎么爬取了,写个函数来实现它:
#urlCur为当前帖子的url,titleCur为帖子标题,nth为当前帖子是第几个人
def getInfo(urlCur, titleCur, nth):
#解析网页
page_content = getPageContent(urlCur)
#定位td标签
contents = page_content.find_all('td',class_='t_f')
subpage = contents[0]
#定位图片,把所有图片的url放到imageList中
images = subpage.select('ignore_js_op')
imageList = []
for i in images:
urlImg = i.find_all('img')[0].get('file')
imageList.append(urlJoin(urlCur, urlImg))
#删除无关文字
while(subpage.i!=None):
subpage.i.extract()
while(subpage.ignore_js_op!=None):
subpage.ignore_js_op.extract()
info = subpage.get_text().strip()
text = '『缘聚睿思【%d】——阿健的爬虫』\n\n' % nth
text += '标题:' + titleCur + '\n\n'
#删除无关的回车和空格,得到最终的文本text
infoSplits = info.splitlines()
for tt in infoSplits:
if len(tt.strip()) == 0:
infoSplits.remove(tt)
for tt in infoSplits:
text += tt + '\n'
return text, imageList
好,对于文本,我们可以直接使用了,但图片的话要先下载下来才能使用。下载之前,我们先在当前目录创建一个文件夹
def mkdir(path):
# 去除首位空格
path=path.strip()
# 去除尾部 \ 符号
path=path.rstrip("\\")
# 判断路径是否存在
isExists=os.path.exists(path)
# 要是存在,先删掉
text = ''
if isExists:
shutil.rmtree(path)
text += ' has been deleted and'
#创建文件夹
os.makedirs(path)
print(path + text + ' created successfully!')
再把这个宝宝所有的照片下载下来
# 传入照片url列表和文件夹名,下载照片,返回照片数
def downloadImgs(imageList, pathName):
# 如果没有照片,返回照片数0
if len(imageList) == 0:
return 0
# 创建文件夹
mkdir(pathName)
# 照片id,保存时取名用
imgID = 0
# 保存所有照片
for image in imageList:
imgID += 1
urllib.request.urlretrieve(image, "%s/%d.jpg" % (pathName,imgID))
numImgs = imgID
# 返回照片数
return numImgs
好了,当前这个宝宝的文本和图片我们都有了,考虑一下怎么发送到微信。我用了itchat这个包,登录后,可以把文本和图片发送给自己、好友和群聊,功能很强大,详情请参考itchat应用教程或校招提醒机器人-肖洒的通俗解释。直接上代码:
'''
输入要发送的文本,图片路径,图片数量,发送对象
com=1,发送给自己
com=2,发送给好友
com=3,发送给群聊
'''
def wx(info, pathName, numImgs, com):
itchat.auto_login(hotReload = True)# 可设置hotReload = True
if com == 1:
userName = 'filehelper'
elif com == 2:
userName = itchat.search_friends(name=u'硕士-徐赛')[0]["UserName"]
else:
i = itchat.get_chatrooms(update=True)
name = "这一家子不简单"
iRoom = itchat.search_chatrooms(name)
for room in iRoom:
if room['NickName'] == name:
userName = room['UserName']
break
itchat.send_msg(info, userName)
if numImgs == 0:
itchat.send_msg('【TA暂时没有美照】', userName)
else:
for i in range(0, numImgs):
path = pathName + '/' + str(i+1) + '.jpg'
itchat.send_image(path,toUserName=userName)
这个函数运行时,会像微信电脑版一样提醒你在手机端登录,登录后就能自动把信息发送到指定对象了。
好,现在,对某个宝宝,我们可以爬取信息并发送到微信了,再考虑爬取其他宝宝的信息。回到缘聚睿思版块,按帖子发布时间排序,它是这样的
我们只爬取普通帖,不爬取投票帖(就是图中洗澡尿尿的那个,我也是醉了),区别就在于标题前面的图标不同。那就通过普通帖的图标,定位到该帖,我们要得到3个信息,该帖的标题、id和url。
标题就不用说了,url也好说,就是为了跳转到帖子内部去爬取文本内容和图片的,最重要的是id,每个帖子都有自己的id。因为我们要循环爬取帖子,为了避免重复抓取,必须要在爬取前判断该帖是否已经爬过,所以一定到得到每个帖子的唯一标识id!
# 论坛网址和每次要爬取的帖子数量
# 输出每个帖子的url、id、标题
def getUrlList(urlHome, numInfo):
page_content = getPageContent(urlHome)
subContents = page_content.find_all('img',{'src':'static/image/common/folder_new.gif'})
urlList = []
idList = []
titleList = []
for i in subContents:
urlList.append('http://rs.xidian.edu.cn/' + i.parent.get('href'))
idList.append(i.parent.parent.parent.parent.get('id'))
titleList.append(i.parent.parent.parent.find_all('a',{'class':'s xst'})[0].get_text())
return urlList[:numInfo],idList[:numInfo],titleList[:numInfo]
再写一个函数,来实现得到所有宝宝帖子的链接、爬取每个宝宝的信息、把信息发送到微信的功能。
# 输入:论坛地址,爬取过的宝宝的id记录,这是第几个宝宝,要爬几个宝宝,发送给谁
# 返回:爬取过的宝宝的id记录,这是第几个宝宝
def xdLoveEachTime(urlHome, idList, nth, numInfo,com):
[urlList,curIdList,curTitleList] = getUrlList(urlHome, numInfo)
for urlCur,idCur,titleCur in zip(urlList,curIdList,curTitleList):
if idCur in idList:
continue
else:
idList.append(idCur)
[info, imageList] = getInfo(urlCur,titleCur, nth)
pathName = 'images'
numImgs = downloadImgs(imageList, pathName)
wx(info,pathName,numImgs,com)
nth += 1
return idList,nth
好,只要给定输入,运行xdLoveEachTime函数,就能在微信端获取每个宝宝的信息了,但我们希望能实时获取最新宝宝的信息,那就加个定时器,每隔一段时间运行一次,保证能拿到第一手的资料。
写一个主函数,把前面所有的功能都封装好,每次要爬几个人、发送给谁等,完全由这个主函数控制,不再需要去修改其他函数的信息
# 输入:论坛网址,发送对象,要爬取几个人,隔多长时间爬取一次
def xdLove(urlHome,com=1,numInfo=1,sleepTime=60):
idList = []
nth = 1
# 这里可以改成while(true)死循环
for i in range(1000):
[idList,nth] = xdLoveEachTime(urlHome,idList,nth,numInfo,com)
print('阿健的爬虫已爬取' + str(i+1) + '次')
time.sleep(sleepTime)
最后,运行主函数就可以了
# 注意论坛的网址一定是这个,这是按发帖时间排序的
urlHome = 'http://rs.xidian.edu.cn/forum.php?mod=forumdisplay&fid=217&filter=author&orderby=dateline'
xdLove(urlHome)
后台的运行界面是这样的
微信端效果参考前面的图片。
至此,我们已经实现了从微信端实时获取睿思最新相亲帖的功能了。现在,你就可以等着喜欢的女嘉宾登场了,是不是有种翻牌儿的感觉~
不过,还是很多地方需要改进一下的:
获取的宝宝是男是女都有可能,我希望每次推荐的宝宝都是女生
即使是在普通帖子中,也会有很多租房帖子之类奇葩帖,我不想看到这些东西
我希望帖主对帖子更新后,我也能看到更新信息。比如,某位女嘉宾希望找一米八的,但后来降低了要求,更新了帖子,改成了一米二的,那我就符合条件啦~
前两个功能可能会加一些自然语言处理的算法来预测一下,再判断是否过滤,还是很有挑战性的;第三个功能其实还不确定是否能看到更新帖,这个就再说吧~