QQ空间说说爬虫
闲来无事,写了一个QQ空间的爬虫,主要是爬取以前的说说,然后生成词云。
这次采用的主要模块是selenium,这是一个模拟浏览器的模块,一开始我不想用这个模块写的,但是后面分析的时候,发现QQ空间的数据加密有点复杂,也没有找到好用的接口,正好又有在学习这个模块,然后就直接用这个模块获取了,这个模块的好处就是不用去纠结传输的过程是如何加密的。
思路
爬取数据模块
通过selenium模块来模拟浏览器登录QQ空间的操作,进入到说说页面,获取说说的总数目和总页数,从最后一页最后一条,即用户的第一条说说开始爬取,存入MongoDB,重复操作直到爬完最后一条。
词云模块
从数据库读取数据,设置图片(可选),生成词云。
整体思路不难,只是有点地方需要注意下:
- 在进入到QQ空间的登录页面的时候
https://i.qq.com/
先给出的是一个扫码登录的窗口
需要先点击一下『帐号密码登录』才可以进入到输入的界面
还有个比较恶心的地方就是,一开始登录进去的是『个人中心』的界面,那个从个人中心页面选择说说的按钮我一直点击不了,也可能是对JS不太了解的原因,后面直接通过URL跳转到『我的主页』,因为浏览器自带cookie的原因,直接跳转过去是成功的。
我不懂其他账号会不会有这种情况,就是有时候他会弹出黄砖过期的广告,如果不点击的话,就无法进入到下一步的操作,有时候又不弹,所以我这边写了个等待十秒,如果有的话,就关闭,没有就直接进入下一步。
还有一个地方,因为进入到『说说』板块的时候,第一页显示你最近发的说说,因为我想从第一条开始爬,所以跳转到最后一页,然后从最后一条一直往上爬,就可以爬取到第一条说说到最后一条说说,这里出现的问题是,一开始我只是
time.sleep(1)
,导致后面爬取的数据是从第一页先爬,然后再爬最后一页。一开始我调试的时候,没有注意到是时间的问题,在解决这个问题的时候,还花了点时间,后面只需把time.sleep
给延长点就解决了,很奇怪的是,明明显示的是更新后的页面,传送过去的页面并没有是最新的,可能是缓存机制。
剩下的也就没有什么难度了,都是些基本操作,这次的爬虫为了实现模拟是人在使用浏览器进行操作,整体的延迟等待还是会有点多的,不需要的可以自行设置延时时间。
代码
代码如下,该注释的都注释了,基本都能看得懂,也都是些很基础的代码。
# -*-coding:utf-8-*-
# Author: AnswerW3I
# version:3.6.3
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
import numpy
from PIL import Image
import time
from bs4 import BeautifulSoup
import matplotlib.pyplot as plt
from wordcloud import WordCloud
import jieba
import pymongo
class QZone_Spider(object):
def __init__(self, url):
self.url = url
# self.Browser = webdriver.Chrome() # 可以看到程序的执行流程
self.Browser = self._browser() # 无头模式,看不到流程,提高程序的效率
self.Browser.get(url)
self.wait = WebDriverWait(self.Browser, 10) # 显示等待,等待网页加载
self.talks = 1 # 从第一条说说开始计数
self.client = pymongo.MongoClient('localhost', port=27017) # 数据库连接
self.db = self.client.test
self.collection = self.db.QQZone
def _browser(self):
"""
:return: 返回一个无头浏览器,禁止加载图片
"""
chrome_options = Options()
prefs = {'profile.default_content_setting_values':{
'images':2
}
} # 禁止浏览器加载图片,提高浏览器运行速度
chrome_options.add_experimental_option('prefs', prefs)
chrome_options.add_argument('window-size=1700x938') # 设置窗口大小,这个很重要,不然无头模式下无法加载页面,会报错
chrome_options.add_argument('--headless')
chrome_options.add_argument('--disable-gpu')
Browser = webdriver.Chrome(chrome_options=chrome_options)
Browser.set_window_size(width=1700, height=1000)
return Browser
def _login(self, user, password):
"""
登录用户
"""
self.Browser.switch_to.frame('login_frame')
time.sleep(1)
login_button = self.Browser.find_element(By.ID, 'switcher_plogin')
login_button.click()
time.sleep(1)
input_name = self.Browser.find_element(By.ID, 'u')
input_password = self.Browser.find_element(By.ID, 'p')
button = self.Browser.find_element(By.ID, 'login_button')
input_name.send_keys(user)
time.sleep(1)
input_password.send_keys(password)
time.sleep(1)
button.click()
time.sleep(5)
self.talks_url = "https://user.qzone.qq.com/" + user + "/main" # qq空间主页
def _get_talks(self, url):
"""
获取说说内容
"""
self.Browser.get(url)
time.sleep(10)
self._close_yellow_page(self.Browser.page_source)
talks_button = self.Browser.find_element(By.ID, 'QM_Profile_Mood_Cnt')
self.talks_num = talks_button.text
print("talks:"+ self.talks_num)
talks_button.click()
time.sleep(5)
self.Browser.switch_to.frame('app_canvas_frame') # 进入到说说的frame
self.pages = self._get_all_pages()
for page in range(self.pages, 0, -1): # 从最后一页开始,也就是第一条说说开始爬取
print("开始爬取第{0}页".format(page))
# 获取当前页面的说说
self._get_page(page=page)
print("第{0}页爬取完毕".format(page))
time.sleep(5)
print("OK!")
def _close_yellow_page(self, html):
# 关闭黄砖广告
soup = BeautifulSoup(html, 'lxml')
page = soup.find_all(id="dialog_main_1")
if page != []:
self.Browser.find_element(By.CLASS_NAME, 'qz_dialog_btn_close').click()
def _says(self, html):
"""
爬取说说
:param html: 当前说说的html页面
"""
soup = BeautifulSoup(html, 'lxml')
print("start with {0}".format(self.Browser.find_element(By.CLASS_NAME, 'mod_pagenav_main').find_element(By.CLASS_NAME, 'current').text))
says = soup.select('.feed')
for item in says[::-1]: # 从说说列表的下面开始爬取
print("第{0}条爬取成功".format(self.talks))
self.talks = self.talks + 1
yield {
"say": item.select('.content')[0].text, # 说说内容
"date": item.select('.ft .goDetail')[0]['title'] # 说说发布的时间
}
def _get_all_pages(self):
"""
:return: 说说总页数
"""
return int(self.Browser.find_element(By.ID, 'pager_last_0').text)
def _get_page(self, page):
change_page = self.Browser.find_element(By.CLASS_NAME, 'mod_pagenav_option').find_element(By.CLASS_NAME, 'textinput')
change_page_button = self.Browser.find_element(By.CLASS_NAME, 'mod_pagenav_option').find_element(By.CLASS_NAME, 'bt_tx2')
change_page.send_keys(page)
time.sleep(1)
change_page_button.click() # 进入下一页
self.wait = WebDriverWait(self.Browser, 10)
print(self.Browser.find_element(By.CLASS_NAME, 'mod_pagenav_main').find_element(By.CLASS_NAME, 'current').text)
time.sleep(10) # 这个很重要,不然加载不出新的页面...
for item in self._says(self.Browser.page_source):
self._save(item)
# print(item)
def _save(self, data):
"""
保存到MongDB里面
"""
self.collection.insert(data)
class FenCI(object):
def __init__(self):
self.client = pymongo.MongoClient("localhost", port=27017)
self.db = self.client.test
self.collection = self.db.QQZone
def _get_words(self):
words = ""
for item in self.collection.find({}):
for i in self._cut_words(item['say']):
words = words + i + " "
return words
def _cut_words(self, data):
for item in jieba.cut(data, cut_all=False):
try:
yield item
except Exception as err:
# 这里有时候会爬取到emoji表情,导致编码读取的时候会报错,这里直接pass掉
pass
def _wordColud(self):
"""
显示词云
"""
world_picture = numpy.array(Image.open("C:/Users/Desktop/img.jpg")) # 加载图片的路径,可选项,不需要图片的话,把下面WorCloud下的mask去掉。
wl_space_split = self._get_words()# 获取生成图文的文字
font = r'C:\Windows\Fonts\simfang.ttf'
my_wordcloud = WordCloud(background_color="white", collocations=False, font_path=font, width=1400, height=1400, margin=2, mask=world_picture).generate(wl_space_split) # 设置图片大小
plt.imshow(my_wordcloud)
plt.axis("off")
plt.show()
plt.savefig('QQZone.png') # 保存图片
def main():
url = "https://i.qq.com/"
Spider = QZone_Spider(url)
User = input("User:")
Password = input("Password:")
Spider._login(user=User, password=Password)
Spider._get_talks(Spider.talks_url)
# 显示词云
Picture = FenCI()
Picture._wordColud()
if __name__ =="__main__":
main()
后记
感觉博客还是得多写,不然感觉文章格式都成一个问题。
这篇文章本该前几天就发了的...一直拖拖到现在...然后最近这段时间也有点迷茫,希望接下来能好好调整一下