第一个需求
从新浪主页抓取当天的所有热门内容。有文字就爬取文字,图片就爬图片,其实直接爬取博文的连接就好。
抓取内容以内容作者为主要的id
爬取作者的昵称和发表日期,博文链接,博文标题。
这里不用登录,因为,热门内容主要是在微博首页,主要的要求就是使用selenium渲染工具去采集动态内容。
第一个问题:
新浪微博的采用下拉式更新的方式,所以需要使用selenium去执行js代码完成下拉的操作。
js='window.scrollTo(0,document.body.scrollHeight);'
browser.execute_script(js)
第二个问题:
定位相关的元素,获取需要的内容,并完成清洗。
#用户昵称
name=b[0].find_element_by_xpath('//div[@class="list_des"]/div[@class="subinfo_box clearfix"]/a[2]/span').text
ptime=b[0].find_element_by_xpath('//div[@class="list_des"]/div[@class="subinfo_box clearfix"]/span[@class="subinfo S_txt2"]').text
#整合在一起
browser.find_elements_by_xpath('//div[@class="UG_list_v2 clearfix"]/div[@class="list_des"]/div[@class="subinfo_box clearfix"]')
#完整的信息
b=browser.find_elements_by_xpath('//ul[@class="pt_ul clearfix"]/div[@class="UG_list_v2 clearfix"]/div[@class="list_des"]/div[@class="subinfo_box clearfix"]|//ul[@class="pt_ul clearfix"]/div[@class="UG_list_b"]/div[@class="list_des"]/div[@class="subinfo_box clearfix"]|//ul[@class="pt_ul clearfix"]/div[@class="UG_list_a"]/div[@class="subinfo_box clearfix"]')
#清洗出用户和日期
for i in b:
i.text.split('\n')[0:2]
#获取博文文本内容
b=browser.find_elements_by_xpath('//ul[@class="pt_ul clearfix"]/div[@class="UG_list_v2 clearfix"]/div[@class="list_des"]/h3|//ul[@class="pt_ul clearfix"]/div[@class="UG_list_b"]/div[@class="list_des"]/h3|//ul[@class="pt_ul clearfix"]/div[@class="UG_list_a"]/h3')
#博文链接
content=browser.find_elements_by_xpath('//ul[@class="pt_ul clearfix"]/div[@class="UG_list_v2 clearfix"]/div[@class="list_des"]/h3|//ul[@class="pt_ul clearfix"]/div[@class="UG_list_b"]/div[@class="list_des"]/h3|//ul[@class="pt_ul clearfix"]/div[@class="UG_list_a"]/h3')
content=[i.text.replace('\n','').replace('#','') for i in content]
第三个问题:
等待,因为使用selenium相当于是打开一个浏览器,和正常的浏览器一样。收到网络的影响,加载页面有可能不及时,导致在内容加载前,selenium就判定定位不到相关内容报错。
在这里使用显示等待的方法,并且在每次更新内容时使用time.sleep延迟。下列的方法,会等待10秒,以便于找到ID为下列的元素被加载。
"PCD_pictext_i_v5"
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
WebDriverWait(browser,10).until(EC.presence_of_element_located((By.ID,"PCD_pictext_i_v5")))
下列是完整的代码,最后保存为json:
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
import time
import random
import json
path='F:/NBA_TEAM/slang.json'
option=webdriver.FirefoxOptions()
option.add_argument('-headless')
browser=webdriver.Firefox(options=option)
browser.get('https://weibo.com/?category=0')
WebDriverWait(browser,10).until(EC.presence_of_element_located((By.ID,"PCD_pictext_i_v5")))
num=10
for i in range(num):
# b=browser.find_elements_by_xpath('//ul[@class="pt_ul clearfix"]/div')
#整合在一起
# browser.find_elements_by_xpath('//div[@class="UG_list_v2 clearfix"]/div[@class="list_des"]/div[@class="subinfo_box clearfix"]')
#完整的信息
user_id_time=browser.find_elements_by_xpath('//ul[@class="pt_ul clearfix"]/div[@class="UG_list_v2 clearfix"]/div[@class="list_des"]/div[@class="subinfo_box clearfix"]|//ul[@class="pt_ul clearfix"]/div[@class="UG_list_b"]/div[@class="list_des"]/div[@class="subinfo_box clearfix"]|//ul[@class="pt_ul clearfix"]/div[@class="UG_list_a"]/div[@class="subinfo_box clearfix"]')
#清洗出用户和日期
user_id_time=[i.text.split('\n')[0:2] for i in user_id_time]
#获取博文文本内容
content=browser.find_elements_by_xpath('//ul[@class="pt_ul clearfix"]/div[@class="UG_list_v2 clearfix"]/div[@class="list_des"]/h3|//ul[@class="pt_ul clearfix"]/div[@class="UG_list_b"]/div[@class="list_des"]/h3|//ul[@class="pt_ul clearfix"]/div[@class="UG_list_a"]/h3')
content=[i.text.replace('\n','').replace('#','') for i in content]
#博文链接
content_link=browser.find_elements_by_xpath('//ul[@class="pt_ul clearfix"]/div[@class="UG_list_v2 clearfix"]/div[@class="list_des"]|//ul[@class="pt_ul clearfix"]/div[@class="UG_list_b"]|//ul[@class="pt_ul clearfix"]/div[@class="UG_list_a"]')
content_link=['https:'+i.get_attribute('href') for i in content_link]
js='window.scrollTo(0,document.body.scrollHeight);'
browser.execute_script(js)
time.sleep(random.randint(1,3))
print(i)
data={}
for i in range(len(content_link)):
data[str(i)]={
"name":str(user_id_time[i][0]),
"time":user_id_time[i][1],
"content":content[i],
"content_link":content_link[i]
}
with open(path,'w',encoding='utf-8') as f:
json.dump(data,fp=f,ensure_ascii=False)
print('all')
不过,这种方式需要等待的时间很长,而且,这种脚本的方式,是必须在抓取完所有数据后才能进行保存,我意识到一个问题,这里每一次遍历都重新创建了一个列表,并丢弃了原来的。因为每次循环,都得重最开始的地方开始抓取。这没什么问题,无非就是去重的问题。问题是保存数据的问题。以后使用scrapy抓取数据的时候肯定是边抓取边保存。。我尝试了下拉刷新100次,总的时长是3151秒。差不多52分钟,就算去掉延时的300秒也是47分钟,而总共抓取了800条数据。确实很慢。
在这里插入图片描述
关于去重的问题,使用redis数据库的集合就可以很好的去重,并且保证能够边刷新,边保存,不至于出现意外的时候,数据丢失。
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
import time
import random
import json
import redis
r=redis.Redis(host='localhost',port='6379',decode_responses=True)
path='F:/NBA_TEAM/slang.json'
option=webdriver.FirefoxOptions()
option.add_argument('-headless')
browser=webdriver.Firefox(options=option)
browser.get('https://weibo.com/?category=0')
WebDriverWait(browser,10).until(EC.presence_of_element_located((By.ID,"PCD_pictext_i_v5")))
num=100
start=time.time()
for i in range(num):
print(i)
#完整的信息
user_id_time=browser.find_elements_by_xpath('//ul[@class="pt_ul clearfix"]/div[@class="UG_list_v2 clearfix"]/div[@class="list_des"]/div[@class="subinfo_box clearfix"]|//ul[@class="pt_ul clearfix"]/div[@class="UG_list_b"]/div[@class="list_des"]/div[@class="subinfo_box clearfix"]|//ul[@class="pt_ul clearfix"]/div[@class="UG_list_a"]/div[@class="subinfo_box clearfix"]')
#清洗出用户和日期
user_id_time=[i.text.split('\n')[0:2] for i in user_id_time]
for i in user_id_time:
r.sadd('nameid',i[0])
r.sadd('time',i[1])
r.sm
#获取博文文本内容
content=browser.find_elements_by_xpath('//ul[@class="pt_ul clearfix"]/div[@class="UG_list_v2 clearfix"]/div[@class="list_des"]/h3|//ul[@class="pt_ul clearfix"]/div[@class="UG_list_b"]/div[@class="list_des"]/h3|//ul[@class="pt_ul clearfix"]/div[@class="UG_list_a"]/h3')
content=[i.text.replace('\n','').replace('#','') for i in content]
for i in content:
r.sadd('content',i)
#博文链接
content_link=browser.find_elements_by_xpath('//ul[@class="pt_ul clearfix"]/div[@class="UG_list_v2 clearfix"]/div[@class="list_des"]|//ul[@class="pt_ul clearfix"]/div[@class="UG_list_b"]|//ul[@class="pt_ul clearfix"]/div[@class="UG_list_a"]')
content_link=['https:'+i.get_attribute('href') for i in content_link]
for i in content_link:
r.sadd('contentlink',i)
js='window.scrollTo(0,document.body.scrollHeight);'
browser.execute_script(js)
time.sleep(random.randint(1,3))
end=time.time()
print('运行时间{}'.format(end-start))
print('all')
browser.quit()
第二个需求:
完成登录,从个人新浪微博爬取关注的用户的微博。
抓取内容以内容作者为主要的id
爬取作者的昵称和发表日期,博文内容。
这里主要是使用selenium进行微博登录,截止2020-4-26日,微博网页版登录没有验证,脚本代码如下:
import numpy as np
import cv2
from selenium import webdriver
import time
option=webdriver.FirefoxOptions()
option.add_argument('-headless')
browser=webdriver.Firefox()
browser.get('https://weibo.com/')
browser.get('https://weibo.com/?category=0')
WebDriverWait(browser,10).until(EC.presence_of_element_located((By.ID,"PCD_pictext_i_v5")))
#登录用户
usr=browser.find_element_by_xpath('//input[@id="loginname"]')
usr.clear()
usr.send_keys('**********')
pas=browser.find_element_by_xpath('//input[@class="W_input"]')
pas.click()
pasw=browser.find_element_by_xpath('//input[@class="W_input W_input_focus"]')
pasw.clear()
pasw.send_keys('**********')
browser.find_element_by_xpath('//div[@class="info_list login_btn"]').click()
#跳转到关注人网页
browser.get('https://weibo.com/5748544426/follow?rightmod=1&wvr=6')
#登录后我的用户名
browser.find_element_by_xpath('//div[@class="pf_username"]').text
#获取一页的关注人
fo=browser.find_elements_by_xpath('//div[@class="mod_info"]/div/a[@class="S_txt1"]')
for i in fo:
i.text#关注用户名
i.get_attribute('href')#关注用户主页
#下一页
browser.find_element_by_link_text("下一页").click()
#判断还有没有下一页
while(1):
if browser.find_element_by_link_text("下一页").get_attribute('href') is None:
print('ok')
break
else:
browser.find_element_by_link_text("下一页").click()
#进入主页后
browser.get('https://weibo.com/u/5856472352?from=myfollow_all')
##用户昵称
browser.find_elements_by_xpath('//h1[@class="username"]')[0].text
##获取发表时间
fo=browser.find_elements_by_xpath('//div[@class="WB_from S_txt2"]')
#获取博文内容
fo=browser.find_elements_by_xpath('//div[@class="WB_text W_f14"]')
使用text来获取内容,似乎只有16条
会介绍如何使用scrapy对接selenium完成任务
爬取思路
- 登录
- 获取所有的关注人网页链接
- 访问网页
- 获取时间、昵称、网页
程序设计思路
我是要使用scrapy对接selenium完成我的关注用户博文的爬取。
- 问题1:如何对接selenium?通过编写scrapy的下载器中间件,截胡spider的请求并将其改成使用selenium发起request,然后将渲染后的page_source加载到response的body中返回到spider进行解析。。
- 问题2:如何登录?通过在spider中重写start_request函数,在这个函数中再次调用selenium完成登录并获取cookie。并将cookie定义为一个属性实例,因为selenium是阻塞的,所以完成多个任务会有些麻烦。
在这里我将登陆保存的代码与scrapy分开了,在scrapy中直接获取代码。因为,小项目执行时间短,方便调试代码。
登陆获取cookie代码:
import numpy as np
import cv2
from selenium import webdriver
import time
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.common.by import By
from selenium.webdriver.support import expected_conditions as EC
import requests
import time
import json
option=webdriver.FirefoxOptions()
option.add_argument('-headless')
browser=webdriver.Firefox(options=option)
browser.get('https://weibo.com/5748544426/follow?rightmod=1&wvr=6')
time.sleep(5)
if WebDriverWait(browser,10).until(EC.presence_of_element_located((By.ID,"loginname"))):
#登录用户
print('登录')
time.sleep(10)
usr=browser.find_element_by_xpath('//input[@id="loginname"]')
usr.clear()
usr.send_keys('*******')
browser.find_element_by_xpath('//div[@class="info_list login_btn"]').click()
time.sleep(10)
js='document.getElementById("myBtn").onclick=function(){displayDate()}'
pasw=browser.find_element_by_xpath('//input[@class="W_input W_input_focus"]')
pasw.clear()
pasw.send_keys('******')
browser.find_element_by_xpath('//div[@class="info_list login_btn"]').click()
#跳转到关注人网页
time.sleep(10)
cookie=browser.get_cookies()
browser.quit()
print("jieshu")
browser=webdriver.Firefox()
path="C:/Users/CAPONEKD/sl/sinlangspider/sinlangspider/spiders/cookies.txt"
with open(path, "w") as fp:
json.dump(cookie,fp)
with open(path, "r") as fp:
cook = json.load(fp)
browser.get('https://weibo.com/5748544426/follow?rightmod=1&wvr=6')
for cookie in cook:
browser.add_cookie(cookie)
browser.get('https://weibo.com/5748544426/follow?rightmod=1&wvr=6')
time.sleep(6)
如果,之后在调试代码的时候,账号被封了出现404,重新改密码,并重新获取cookie。
scrapy编写
编写item
这是全篇的数据存储,不过本次项目的结果只有三个,所以数据结构比较简单。
主要是关注用户的ID,博文内容,和发表日期。
# -*- coding: utf-8 -*-
# Define here the models for your scraped items
#
# See documentation in:
# https://docs.scrapy.org/en/latest/topics/items.html
from scrapy import Item, Field
class SinlangspiderItem(Item):
name_id = Field()#用户ID
content = Field()#内容
date = Field()#发表如期
编写spider
完成主要的爬取开始和页面解析,本次项目中,主要是爬取个人的关注用户,和其最近的微博。所以这次涉及到两个方面的问题,一个是爬取个人的关注用户有哪些,另一个是获取各个用户的博文内容。因为是对接selenium所以,会有阻塞,所以是先完成对所有关注用户的爬取,在对关注用户的博文爬取。
import scrapy
from scrapy.http import Request
import time
from sinlangspider import items
class QuotesSpider(scrapy.Spider):
def __init__(self,**kwargs):
self.follow_name = set()
self.follow_url = set()
name = "slan"
allowed_domains = ['weibo.com']#允许的域名
def start_requests(self):
yield Request('https://weibo.com/5748544426/follow?rightmod=1&wvr=6',
callback = self.parse)
def parse(self, response):
print('---------爬取中-------')
follow_info = response.xpath(
'//div[@class="mod_info"]/div/a[@class="S_txt1"]')
for i in follow_info:
self.follow_name.add(i.xpath('text()').get())#关注用户名
self.follow_url.add(i.xpath('@href').get())#关注用户主页
# print(self.follow_name,self.follow_url)
try:
next_page = response.xpath('//a[@class="page next S_txt1 S_line1"]/@href').get()
if next_page:
print('下一页',next_page)
next_page = next_page.split('#')#下一页利用#分割,#后的不要
url = 'https://weibo.com'+next_page[0]
yield Request(url,
callback = self.parse)
else:
new_url='https://weibo.com'+self.follow_url.pop()
yield Request(new_url,
callback = self.parse2)
except:
print('异常没了')
new_url='https://weibo.com'+self.follow_url.pop()
yield Request(new_url,
callback = self.parse2)
def parse2(self,response):
name_id = response.xpath('//h1[@class="username"]/text()').get()
info=response.xpath('//div[@class="WB_detail"]')
for i in info:
date = i.xpath('div[@class="WB_from S_txt2"]/a[@class="S_txt2"][1]/text()').get()
content = i.xpath('div[@class="WB_text W_f14"]/text()').get()
item = items.SinlangspiderItem(name_id = name_id,
content=content,date = date)
yield item
print('完成')
if self.follow_url:
new_url = 'https://weibo.com'+self.follow_url.pop()
print('新的开始',new_url)
yield Request(new_url,
callback = self.parse2)
else:
print('全部结束')
编写pipeline
pipeline主要是用于对接mongoDB数据库。并保存数据。
# -*- coding: utf-8 -*-
# Define your item pipelines here
#
# Don't forget to add your pipeline to the ITEM_PIPELINES setting
# See: https://docs.scrapy.org/en/latest/topics/item-pipeline.html
import pymongo
import json
from scrapy.exceptions import DropItem
class SinlangspiderPipeline(object):
def __init__(self,mongo_url,mongo_db):
self.mongo_url = mongo_url
self.mongo_db = mongo_db
@classmethod
def from_crawler(cls,crawler,**kwargs):
return cls(mongo_url = crawler.settings.get('MONGO_URL'),
mongo_db = crawler.settings.get('MONGO_DATABASE'))
def open_spider(self,spider):
self.client = pymongo.MongoClient(self.mongo_url)
self.db = self.client[self.mongo_db]
self.mycol = self.db['slan']
def close_spider(self,spider):
self.client.close()
def process_item(self, item, spider):
print('开始保存')
if item['name_id']:
self.mycol.insert_one(dict(item))
return item
else:
raise DropItem('信息丢失')
middlewares编写
middlewares主要的作用是用于对接selenium,并且获取给每次请求添加上cookie。并设置随机UA用于反爬。可以的话最好加上代理IP。
class CookieMiddleware(object):
def __init__(self,**kwargs):
self.option=webdriver.FirefoxOptions()
self.option.add_argument('-headless')
self.browser=webdriver.Firefox(options=self.option)
def __del__(self):
self.browser.quit()
def process_request(self, request, spider):
path="C:/Users/CAPONEKD/sl/sinlangspider/sinlangspider/spiders/cookies.txt"
with open(path, "r") as fp:
cook = json.load(fp)
try:
self.browser.get(request.url)
for cookie in cook:
self.browser.add_cookie(cookie)
print('新的url',request.url)
self.browser.get(request.url)
element = WebDriverWait(self.browser,10).until(
EC.presence_of_element_located((By.XPATH, '//h1[@class="username"]')))
return HtmlResponse(
url=request.url, body=self.browser.page_source,
request=request, encoding='utf-8', status=200)
except TimeoutException:
return HtmlResponse(
url=request.url, status=500, request=request)
class RandomUserAgent(object):
# Not all methods need to be defined. If a method is not defined,
# scrapy acts as if the downloader middleware does not modify the
# passed objects.
def __init__(self,agents):
self.agents=agents
@classmethod
def from_crawler(cls, crawler):
# This method is used by Scrapy to create your spiders.
# s = cls()
# crawler.signals.connect(s.spider_opened, signal=signals.spider_opened)
return cls(crawler.settings.getlist('USER_AGENTS'))
def process_request(self, request, spider):
request.headers.setdefault('User-Agent',random.choice(self.agents))
print(request.headers['User-Agent'])
settings设置
#用于添加项目的地址
PROJECT_DIR = os.path.dirname(os.path.abspath(os.path.curdir))
#robots.txt禁用
ROBOTSTXT_OBEY = False
#下载器中间件启用
DOWNLOADER_MIDDLEWARES = {
#'sinlangspider.middlewares.SinlangspiderDownloaderMiddleware': 543,
'sinlangspider.middlewares.RandomUserAgent':100,
'sinlangspider.middlewares.CookieMiddleware':543
}
#mongodburl
MONGO_URL = 'mongodb://localhost:27017/'
#mongo数据库
MONGO_DATABASE = "runoobdb"
#pipeline启用
ITEM_PIPELINES = {
'sinlangspider.pipelines.SinlangspiderPipeline': 300,
}
#设置UA
USER_AGENTS = [
"Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; AcooBrowser; .NET CLR 1.1.4322; .NET CLR 2.0.50727)",
]
最后是使用scrapyd部署
首先安装scrapyd
pip install scrapyd
启动scrapyd:在命令行中输入srapyd就行了,默认情况下scrapyd运行后台会侦听6800端口。在浏览器中输入http://127.0.0.1:6800。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-owhjs8iB-1587900829322)(713A3D3014CF4AD08F5433F579C310B3)]
scrapyd使用json数据返回状态,所以其对应的指令如下
获取scrapyd状态:http://127.0.0.1:6800/daemonstatus.json.GET请求方式。
获取项目列表:http://127.00.1/6800/listprojects.json.GET请求方式。
获取项目下以发布的爬虫列表http://127.0.0.16800/listspiders.json?project=myproject.GET请求。项目名称为myproject。
获取已发布的爬虫版本列表。http://127.0.0.1:6800/listversion.json?project=myproject.GET请求。参数为项目名称project。
获取爬虫运行状态:http://127.0.0.1/6800/listjobs.json?project=myproject.GET请求,
启动服务器上的某一个爬虫:http://127.0.0.1:6800/schedule.json.POST请求方式,蚕食为:"project":myproject,'spider':myspider,myproject为项目名称,myspider为爬虫名称。
删除某一版本爬虫:http://127.0.0.1:6800/delversion.json.POST请求参数为''project':myproject,'version':myversion,myproject为项目名,version为爬虫版本。
删除某一工程,并将工程下各个版本爬虫一起删除。http://127.0.0.1:6800/delproject.json.POST请求方式,参数为'project':myproject,myproject为项目名称。
给工程添加版本,如果工程不存在则创建:http://127..0.1/6800/addversion.json.POST请求方式,参数为""project":myproject,'version':myversion,myproject为项目名称。version为项目版本。
取消一个运行的爬虫任务。http://127.0.0.1:6800/cancel.json.POST请求方式,参数为''project':myproject,'job':jobid,myproject为项目名称,jobid为任务id。
scrapyd-client
scrapyd-client用于发布爬虫,首先安装
pip install scrapyd-client
使用scrapyd-client 安装完成后,将scrapyd-deploy拷贝到爬虫项目目录下,与scrapy.cfg在同一级目录。下面我们要修改scrapy.cfg文件,默认生成的scrapy.cfg文件内容如下:
[settings]
default = sinlangspider.settings
[deploy:100]
url = http://localhost:6800/
project = sinlang
deploy用于表示把爬虫发布到名为100的爬虫服务器上。一般在需要同时发去爬虫到多个目标服务器时使用。
配置完成后,就可以使用scrapy-deploy进行爬虫的发布。命令如下
scrapyd-deploy <target> -p sinlang --version ver2020426
版本如果不设置的话就会默认使用时间戳。
在部署爬虫前,要确认scrapyd启动了。
部署后启动爬虫:
在终端输入:
curl http://localhost:6800/schedule.json -d project=sinlang -d spider=slan
就此全部完成。
查看数据有100条
这个练手的小项目有很多的不足,仅仅满足基本需求。以后有机会会改进的。
网友评论