python selenium爬虫豆瓣_使用selenium+requests爬取豆瓣小组讨论列表
获取本文代码 · 我的GitHub
注:这个项目的代码会在我的GitHub持续优化、更新,而在本文中的代码则是最初版本的代码。
豆瓣小组
豆瓣有一个“小组”模块,有一些小组中会发布很多租房信息。在这里找租房信息的好处就在于,可以避免被那些第三方平台的中介忽悠,有更多的机会直接联系上房东,或有转租、寻求合租需求的人。
但是目前豆瓣租房小组存在的问题就是,信息高度不标准化,每一个人发布的信息的格式都各不相同,想要根据一些条件搜索到自己真正需要的信息比较困难,比如无法根据租金、地段、房型等条件去过滤,只能人工一个个去看,看一天下来,整个人都晕了,还不一定能找到中意的房子。
所以想到,搞一个爬虫呗,很多租房小组还是很活跃的,每天更新的信息量巨大,让人目不暇接,搞个爬虫自动化去爬取这些数据,并做一些简单的筛选,最终呈现在自己眼前,让自己找房子更有效率。
爬虫用到的技术点
使用selenium模拟登录,获取cookie,基本用法可以参见我的另一篇文章:使用selenium+requests登录网页并持久化cookie
使用requests库+cookie发送请求,获取数据。
使用lxml库和xpath语法解析网页数据,整理数据。
使用jinja2模板引擎渲染数据到HTML网页中,结构化地展示出来。
完整代码
下面的代码爬取了一个豆瓣租房小组的1000条讨论列表,从中筛选出了含有某些关键词的条目。假设将下面的代码保存在spider.py文件,则运行方式为:python spider.py 豆瓣用户名 豆瓣用户密码 讨论起始位置 要爬取的条数,代码中有详细的注释:
# coding:utf-8
# 豆瓣爬虫核心方法
from __future__ import unicode_literals
from selenium import webdriver
import requests
import time
import json
from lxml import etree
import random
from operator import itemgetter
from jinja2 import Environment, FileSystemLoader
import sys
reload(sys)
sys.setdefaultencoding('utf-8')
class DoubanSpider(object):
'''
豆瓣爬虫
'''
def __init__(self, user_name, password, headless = False):
'''
初始化
:param user_name: 豆瓣登录用户名
:param password: 豆瓣登录用户密码
:param headless: 是否显示webdriver浏览器窗口
:return: None
'''
self.user_name = user_name
self.password = password
self.headless = headless
# 登录
self.login()
def login(self):
'''
登录,并持久化cookie
:return: None
'''
# 豆瓣登录页面URL
login_url = 'https://www.douban.com/accounts/login'
# 获取chrome的配置
opt = webdriver.ChromeOptions()
# 在运行的时候不弹出浏览器窗口
if self.headless:
opt.set_headless()
# 获取driver对象
self.driver = webdriver.Chrome(chrome_options = opt)
# 打开登录页面
self.driver.get(login_url)
print '[login] opened login page...'
# 向浏览器发送用户名、密码,并点击登录按钮
self.driver.find_element_by_name('form_email').send_keys(self.user_name)
self.driver.find_element_by_name('form_password').send_keys(self.password)
# 多次登录需要输入验证码,这里给一个手工输入验证码的时间
time.sleep(6)
self.driver.find_element_by_class_name('btn-submit').submit()
print '[login] submited...'
# 等待2秒钟
time.sleep(2)
# 创建一个requests session对象
self.session = requests.Session()
# 从driver中获取cookie列表(是一个列表,列表的每个元素都是一个字典)
cookies = self.driver.get_cookies()
# 把cookies设置到session中
for cookie in cookies:
self.session.cookies.set(cookie['name'],cookie['value'])
def get_page_source(self, url):
'''
获取浏览器窗口中的页面HTML内容
:param url: 网页链接
:return: 网页页面HTML内容
'''
self.driver.get(url)
page_source = self.driver.page_source
print '[get_page_source] page_source head 100 char = {}'.format(page_source[:100])
return page_source
def get(self, url, params = None):
'''
向一个url发送get请求,返回response对象
:param url: 网页链接
:param params: URL参数字典
:return: 发送请求后获取的response对象
'''
# 等待一个随机的时间,防止被封IP,这里随机等待0~6秒,亲测可有效地避免触发豆瓣的反爬虫机制
time.sleep(6 * random.random())
resp = self.session.get(url, params = params, headers = self.get_headers())
if resp:
print '[get] url = {0}, status_code = {1}'.format(url, resp.status_code)
resp.encoding = 'utf-8'
# 这里很重要,每次发送请求后,都更新session的cookie,防止cookie过期
if resp.cookies.get_dict():
self.session.update(resp.cookies)
print '[get] updated cookies, new cookies = {0}'.format(resp.cookies.get_dict())
return resp
else:
print '[get] url = {0}, response is None'.format(url)
return None
def get_html(self,url, params = None):
'''
获取一个url对应的页面的HTML代码
:param url: 网页链接
:param params: URL参数字典
:return: 网页的HTML代码
'''
resp = self.get(url)
if resp:
return resp.text
else:
return ''
def get_headers(self):
'''
随机获取一个headers
'''
user_agents = ['Mozilla/5.0 (Windows NT 6.1; rv:2.0.1) Gecko/20100101 Firefox/4.0.1','Mozilla/5.0 (Windows; U; Windows NT 6.1; en-us) AppleWebKit/534.50 (KHTML, like Gecko) Version/5.1 Safari/534.50','Opera/9.80 (Windows NT 6.1; U; en) Presto/2.8.131 Version/11.11']
headers = {'User-Agent':random.choice(user_agents)}
return headers
class DoubanDiscussionSpider(DoubanSpider):
'''
豆瓣小组讨论话题爬虫
'''
def __init__(self, user_name, password, group_name, headless = False):
'''
初始化
:param user_name: 豆瓣登录用户名
:param password: 豆瓣登录用户密码
:param group_name: 豆瓣小组名称
:param headless: 是否显示webdriver浏览器窗口
:return: None
'''
super(DoubanDiscussionSpider,self).__init__(user_name, password, headless)
self.group_name = group_name
# 豆瓣小组讨论列表URL模板
self.url_tpl = 'https://www.douban.com/group/{group_name}/discussion?start={start}&limit={limit}'.format(group_name = self.group_name,start = '{start}', limit = '{limit}')
print '[__init__] url = {0}'.format(self.url_tpl)
def get_discussion_list(self, start=0, limit=100, filter = []):
'''
获取讨论列表
:param start: 开始条目数,默认值为0
:param limit: 总条数,默认值为100,最大值也为100
:param filter: 关键词列表,只过滤出标题或详情中含有关键词列表中关键词的项
:return: 话题讨论内容字典列表
'''
list_url = self.url_tpl.format(start = start, limit = limit)
page_html = self.get_html(list_url)
html = etree.HTML(page_html)
# 解析话题讨论列表
trs = html.xpath('//*[@class="olt"]/tr')[1:]
# 话题字典列表
topics = []
# 已结被添加的topic link列表,用于去重
added_links = []
for tr in trs:
title = tr.xpath('./td[1]/a/text()')[0].strip()
link = tr.xpath('./td[1]/a/@href')[0].strip()
# 继续解析话题详情页面,从中解析出发布时间、描述详情
topic_page_html = self.get_html(link)
topic = etree.HTML(topic_page_html)
# 发布时间字符串
post_time_str = topic.xpath('//*[@class="topic-doc"]/h3[1]/span[2]/text()')[0].strip()
# 详情
detail = topic.xpath('//*[@class="topic-content"]')[0].xpath('string(.)').strip()
# 根据关键词过滤
if filter and not self.contains(title, filter) and not self.contains(detail, filter):
continue
topic_dict = {}
topic_dict['title'] = title
topic_dict['link'] = link
if link in added_links:
continue
else:
added_links.append(link)
topic_dict['post_time_str'] = post_time_str
topic_dict['post_time'] = time.mktime(time.strptime(post_time_str,'%Y-%m-%d %H:%M:%S'))
topic_dict['detail'] = detail
topics.append(topic_dict)
print '[get_discussion_list] parse topic: {0} finished'.format(link)
print '[get_discussion_list] get all topics finished, count of topics = {0}'.format(len(topics))
# 对topics按照发布时间排序(降序)
topics = sorted(topics, key = itemgetter('post_time'), reverse = True)
return topics
def get_discussion_list_cyclely(self, start = 0, limit = 100, filter = []):
'''
循环获取讨论列表
:param start: 开始条目数,默认值为0
:param limit: 总条数,默认值为100
:param filter: 关键词列表,只过滤出标题或详情中含有关键词列表中关键词的项
:return: 话题讨论内容字典列表
'''
topics = []
if limit <= 100:
topics = self.get_discussion_list(start, limit, filter)
else:
for start in range(start, limit, 100):
topics.extend(self.get_discussion_list(start, 100, filter))
print '[get_discussion_list_cyclely] get all topics finished, count of topics = {0}'.format(len(topics))
# 对topics按照发布时间排序(降序)
topics = sorted(topics, key = itemgetter('post_time'), reverse = True)
return topics
def contains(self, text, filter):
'''
判断一个字符串中是否包含了一个关键词列表中的至少一个关键词
:param text: 字符串
:param filter: 关键词字符串列表
:return: bool
'''
for kw in filter:
if kw in text:
return True
return False
def render_topics(self, topics):
'''
把topic列表内容渲染到HTML文件中
:param topics: topic列表
:return: None
'''
env = Environment(loader = FileSystemLoader('E:/code/py-project/DoubanHouse/'))
tpl = env.get_template('topics_tpl.html')
with open('topics.html','w+') as fout:
render_content = tpl.render(topics = topics)
fout.write(render_content)
print '[render_topics] render finished'
def sample():
'''
测试
'''
# 豆瓣账号用户名、密码
user_name = sys.argv[1]
password = sys.argv[2]
# 起始位置
start = int(sys.argv[3])
# 打算爬取的小组讨论条数
limit = int(sys.argv[4])
# 小组名称
group_name = 'nanshanzufang'
# 创建爬虫spider对象
spider = DoubanDiscussionSpider(user_name, password, group_name)
# 获取当前小组的话题列表,按照关键词列表过滤内容,只有讨论的标题或详情中包含这个列表中至少一个关键词的时候,才保留
filter = ['主卧','主人','独卫','甲醛','大卧','独立卫生间']
topics = spider.get_discussion_list_cyclely(start,limit, filter)
# 将topics列表内容渲染到HTML表格中
spider.render_topics(topics)
if __name__ == '__main__':
sample()
print 'end'
代码中用的的jinja2模板文件topics_tpl.html的HTML代码如下:
table.gridtable {
font-family: verdana,arial,sans-serif;
font-size:11px;
color:#333333;
border-width: 1px;
border-color: #666666;
border-collapse: collapse;
}
table.gridtable th {
border-width: 1px;
padding: 8px;
border-style: solid;
border-color: #666666;
background-color: #dedede;
}
table.gridtable td {
border-width: 1px;
padding: 8px;
border-style: solid;
border-color: #666666;
background-color: #ffffff;
}
标题 | 发布时间 | 链接 |
---|
{%- for topic in topics -%}
{{topic['title']}}{{topic['post_time_str']}}{{topic['link']}}
{% endfor %}
运行上面python代码,最终得到的渲染出来的topics.html文件的内容是这样的:
过程中遇到的坑和不足之处
首先就是一开始,一下子在十几秒钟之内发送了几百上千个请求 ,过于频繁,被豆瓣封了账号、封了IP,十分悲催。后来痛定思痛,采用了一个简单的策略,就解决了这个问题,那就是在每次请求之间设置随机几秒的间隔时间,就有效地避免了被豆瓣反爬虫机制当成“机器”封账号和IP。
每次启动程序,都需手工输入验证码,然后登录,比较不方便。以后有时间可以优化为自动识别验证码图片、自动填写验证码。
python selenium爬虫豆瓣_使用selenium+requests爬取豆瓣小组讨论列表相关推荐
- Python数据爬虫学习笔记(3)爬取豆瓣阅读的出版社名称数据
需求:写一个Python程序,实现获取豆瓣阅读网页上的所有出版社名称,并储存在指定路径的txt文件中,如下: 准备:观察该网页的源代码,注意到每个出版社名称都由固定格式的div所包裹,如下: 编写代码 ...
- Python爬虫小白教程(二)—— 爬取豆瓣评分TOP250电影
文章目录 前言 安装bs4库 网站分析 获取页面 爬取页面 页面分析 其他页面 爬虫系列 前言 经过上篇博客Python爬虫小白教程(一)-- 静态网页抓取后我们已经知道如何抓取一个静态的页面了,现在 ...
- day02:requests爬取豆瓣电影信息
一.requests爬取豆瓣电影信息 -请求url: https://movie.douban.com/top250 -请求方式: get -请求头: user-agent cookies二.sele ...
- 爬虫实战2(下):爬取豆瓣影评
上篇笔记我详细讲诉了如何模拟登陆豆瓣,这次我们将记录模拟登陆+爬取影评(复仇者联盟4)实战.本文行文结构如下: 模拟登陆豆瓣展示 分析网址和源码爬取数据 进行面对对象重构 总结 一.模拟登陆 ...
- 爬虫实战2(上):爬取豆瓣影评
这次我们将主要尝试利用python+requsets模拟登录豆瓣爬取复仇者联盟4影评,首先让我们了解一些模拟登录相关知识补充.本文结构如下: request模块介绍与安装 get与post方式介 ...
- python爬取豆瓣电影并分析_Python爬取豆瓣电影的短评数据并进行词云分析处理
前言 对于爬虫很不陌生,而爬虫最为经典的案例就是爬取豆瓣上面的电影数据了,今天小编就介绍一下如果爬取豆瓣上面电影影评,以<我不是药神>为例. 基本环境配置 版本:Python3.6 相关模 ...
- python爬虫怎么爬小说_小白的python爬虫,40代码教你爬取豆瓣小说
这篇文章写了很久了,一直没有发布: 爬虫学的差不多了,觉得这篇文章对新手实践还是有些作用的.毕竟这也是我刚学爬虫的时候练习的,爬取了比较好爬的网站,也比较经典:多余的解释不说了,代码里每一行都有注释, ...
- python爬取豆瓣代码_小白的python爬虫,40代码教你爬取豆瓣小说
这篇文章写了很久了,一直没有发布: 爬虫学的差不多了,觉得这篇文章对新手实践还是有些作用的.毕竟这也是我刚学爬虫的时候练习的,爬取了比较好爬的网站,也比较经典:多余的解释不说了,代码里每一行都有注释, ...
- 【爬虫案例】Requests爬取豆瓣短评以及入门爬虫注意事项
一.Requests是什么? Requests 是一个 Python 的 HTTP 客户端库. 支持的 HTTP 特性: 保持活动和连接池.国际域名和URL.Cookie持久性会话 浏览器式SSL验证 ...
最新文章
- 关于EXCEL超级链接被禁止的解决方法
- 面试题6:输入一个链表,按链表值从尾到头的顺序返回一个ArrayList
- ubuntu-server-18.04 设置开机启动脚本
- Spring Boot2整合Shiro(1):身份认证
- java中entry_Java FastMap.Entry方法代码示例
- java版 二叉树 所有递归和非递归遍历算法
- mysql导入报错1071_导入sql文件报错:1071 Specified key was too long; max key length is 767 bytes...
- 前后端分离 前端页面验证码没有显示
- python学爬虫、还没放出来_Python 爬虫学习没思路?可以看看这篇文章
- 腾讯2014在广州站实习生offer经验(TEG-开发背景)
- 如何破解WP7并安装xap文件
- 二进制转8421bcd码_绝对值编码器当中的格雷码
- 二哥杂货铺matlab安装步骤,Matlab2017a软件安装教程
- python logging日志分割_Python3测井曲线切割,python3logging,日志
- android音频格式,适用于iOS和Android的音频格式
- FC1179U盘量产教程
- Vmware中桥接无法获取IP
- 关于PC端QQ无法加载群文件和打开在线群文件解决方法
- html5 css3 内边距,css什么是内边距?css内边距的设置方法(实例)
- 计算机考研公共课考英语几,新文道教育:2022考研必须要了解的30个知识点
热门文章
- 简单的python抢红包脚本-Python自动抢红包,超详细教程,再也不会错过微信红包了...
- Polar码快速入门
- Flutter (仿微信通讯录)按字母分组列表
- linux远ssh sed,ssh远程执行sed -i命令,替换的内容中含有双引号的问题
- 微信中控网页授权的实现
- u盘乱码怎么办?数据丢失如何恢复?(详解)
- python用泰勒级数计算圆周率_Python中利用进度条求圆周率
- 微信上网卡WeSim悄然发布
- 学习笔记10--CAN总线技术
- KeyError: ‘label‘