获取本文代码 · 我的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爬取豆瓣小组讨论列表相关推荐

  1. Python数据爬虫学习笔记(3)爬取豆瓣阅读的出版社名称数据

    需求:写一个Python程序,实现获取豆瓣阅读网页上的所有出版社名称,并储存在指定路径的txt文件中,如下: 准备:观察该网页的源代码,注意到每个出版社名称都由固定格式的div所包裹,如下: 编写代码 ...

  2. Python爬虫小白教程(二)—— 爬取豆瓣评分TOP250电影

    文章目录 前言 安装bs4库 网站分析 获取页面 爬取页面 页面分析 其他页面 爬虫系列 前言 经过上篇博客Python爬虫小白教程(一)-- 静态网页抓取后我们已经知道如何抓取一个静态的页面了,现在 ...

  3. day02:requests爬取豆瓣电影信息

    一.requests爬取豆瓣电影信息 -请求url: https://movie.douban.com/top250 -请求方式: get -请求头: user-agent cookies二.sele ...

  4. 爬虫实战2(下):爬取豆瓣影评

       上篇笔记我详细讲诉了如何模拟登陆豆瓣,这次我们将记录模拟登陆+爬取影评(复仇者联盟4)实战.本文行文结构如下: 模拟登陆豆瓣展示 分析网址和源码爬取数据 进行面对对象重构 总结   一.模拟登陆 ...

  5. 爬虫实战2(上):爬取豆瓣影评

       这次我们将主要尝试利用python+requsets模拟登录豆瓣爬取复仇者联盟4影评,首先让我们了解一些模拟登录相关知识补充.本文结构如下: request模块介绍与安装 get与post方式介 ...

  6. python爬取豆瓣电影并分析_Python爬取豆瓣电影的短评数据并进行词云分析处理

    前言 对于爬虫很不陌生,而爬虫最为经典的案例就是爬取豆瓣上面的电影数据了,今天小编就介绍一下如果爬取豆瓣上面电影影评,以<我不是药神>为例. 基本环境配置 版本:Python3.6 相关模 ...

  7. python爬虫怎么爬小说_小白的python爬虫,40代码教你爬取豆瓣小说

    这篇文章写了很久了,一直没有发布: 爬虫学的差不多了,觉得这篇文章对新手实践还是有些作用的.毕竟这也是我刚学爬虫的时候练习的,爬取了比较好爬的网站,也比较经典:多余的解释不说了,代码里每一行都有注释, ...

  8. python爬取豆瓣代码_小白的python爬虫,40代码教你爬取豆瓣小说

    这篇文章写了很久了,一直没有发布: 爬虫学的差不多了,觉得这篇文章对新手实践还是有些作用的.毕竟这也是我刚学爬虫的时候练习的,爬取了比较好爬的网站,也比较经典:多余的解释不说了,代码里每一行都有注释, ...

  9. 【爬虫案例】Requests爬取豆瓣短评以及入门爬虫注意事项

    一.Requests是什么? Requests 是一个 Python 的 HTTP 客户端库. 支持的 HTTP 特性: 保持活动和连接池.国际域名和URL.Cookie持久性会话 浏览器式SSL验证 ...

最新文章

  1. 关于EXCEL超级链接被禁止的解决方法
  2. 面试题6:输入一个链表,按链表值从尾到头的顺序返回一个ArrayList
  3. ubuntu-server-18.04 设置开机启动脚本
  4. Spring Boot2整合Shiro(1):身份认证
  5. java中entry_Java FastMap.Entry方法代码示例
  6. java版 二叉树 所有递归和非递归遍历算法
  7. mysql导入报错1071_导入sql文件报错:1071 Specified key was too long; max key length is 767 bytes...
  8. 前后端分离 前端页面验证码没有显示
  9. python学爬虫、还没放出来_Python 爬虫学习没思路?可以看看这篇文章
  10. 腾讯2014在广州站实习生offer经验(TEG-开发背景)
  11. 如何破解WP7并安装xap文件
  12. 二进制转8421bcd码_绝对值编码器当中的格雷码
  13. 二哥杂货铺matlab安装步骤,Matlab2017a软件安装教程
  14. python logging日志分割_Python3测井曲线切割,python3logging,日志
  15. android音频格式,适用于iOS和Android的音频格式
  16. FC1179U盘量产教程
  17. Vmware中桥接无法获取IP
  18. 关于PC端QQ无法加载群文件和打开在线群文件解决方法
  19. html5 css3 内边距,css什么是内边距?css内边距的设置方法(实例)
  20. 计算机考研公共课考英语几,新文道教育:2022考研必须要了解的30个知识点

热门文章

  1. 简单的python抢红包脚本-Python自动抢红包,超详细教程,再也不会错过微信红包了...
  2. Polar码快速入门
  3. Flutter (仿微信通讯录)按字母分组列表
  4. linux远ssh sed,ssh远程执行sed -i命令,替换的内容中含有双引号的问题
  5. 微信中控网页授权的实现
  6. u盘乱码怎么办?数据丢失如何恢复?(详解)
  7. python用泰勒级数计算圆周率_Python中利用进度条求圆周率
  8. 微信上网卡WeSim悄然发布
  9. 学习笔记10--CAN总线技术
  10. KeyError: ‘label‘