Python 实战-第 1 周-练习项目03-爬取租房信息
成果展示
代码
贴代码如下。同时放在 GitHub 库 上
#coding: utf-8
from bs4 import BeautifulSoup as bs
import requests
# CONSTANTS
FEMALE_TIP = '\xe5\xa5\xb9'
GENDER_TIP = u'\u6027\u522b\uff1a'
FEMALE = '\xe5\xa5\xb3'
MALE = '\xe7\x94\xb7'
# Source
urlFather = 'http://bj.xiaozhu.com/search-duanzufang-p1-0/'
webDataFather = requests.get(urlFather)
soupFather = bs(webDataFather.text, 'lxml')
url = (soupFather.select('#page_list > ul > li > a'))[0].get('href')
webData = requests.get(url)
soup = bs(webData.text, 'lxml')
# Raw data
title = [i.get_text() for i in soup.select('div.pho_info > h4 > em')][0]
address = [i.get_text() for i in soup.select('body > div.wrap.clearfix.con_bg > div.con_l > div.pho_info > p > span.pr5')][0]
feed_pd = [i.get_text() for i in soup.select('#pricePart > div.day_l > span')][0]
imgHouse001 = [i.get('src') for i in soup.select('#curBigImage')][0]
nameLandlord = [i.get('title') for i in soup.select('#floatRightBox > div.js_box.clearfix > div.w_240 > h6 > a')][0]
imgLandlord = [i.get('src') for i in soup.select('#floatRightBox > div.js_box.clearfix > div.member_pic > a > img')][0]
profileURL = [i.get('href') for i in soup.select('#floatRightBox > div.js_box.clearfix > div.w_240 > h6 > a')][0]
profileGet = requests.get(profileURL)
profileSoup = bs(profileGet.text, 'lxml')
if 0 == len(profileSoup.find_all("ul", class_="fd_person")):
genderStatement = [i.get_text() for i in profileSoup.select('body > div.contentFD.clearfix > div.fd_con > \
div.fd_infor > dl > dt')][0]
if FEMALE_TIP == genderStatement[0].encode('utf-8'):
gender = FEMALE
else:
gender = MALE
else:
gender = [i.get_text() for i in profileSoup.select('ul.fd_person > li:nth-of-type(1)')][0].strip(GENDER_TIP).encode('utf-8')
print('[Title] {}\n[Address] {}\n[Price] {}\n[Image URL(download it and open in Chrome)] {}'.format(title.encode('utf-8'), address.encode('utf-8'),
feed_pd, imgHouse001))
print('[Information about the landlord]')
print(' [Name ] {}'.format(nameLandlord.encode('utf-8')))
print(' [Gender] {}'.format(gender))
print(' [Image ] {}'.format(imgLandlord))
总结
其实这次的练习基本上没什么明显的大坑,主要是我的网络不知道为什么被小猪短租屏蔽了……并不是爬取之后被屏蔽,而是第一次想打开就没法直接打开,搬了梯子……
0. 如何得到汉字的 UTF-8 编码?
在 Terminal 中打开 python,输入代码:
ChineseChar = '她'
HER = ChineseChar.encode('utf-8')
即可在变量 HER
中存储汉字「她」的 UTF-8 编码
1. 图片打不开,怎么办?(踩坑)
通过检查网页(例如 http://bj.xiaozhu.com/fangzi/1657828135.html )源代码抠出来的图片对应的地址(例如http://image.xiaozhustatic1.com/00,800,533/3,0,58,5069,1800,1200,9bfff1c8.jpg )下载的图片没法直接打开?在 Debian GNU/Linux 8 (jessie) 64-bit 和 Windows 7(64-bit) 上都如此。
解决办法是:尝试把这个下载的图片拖到 Chrome 中看看,通常就能打开了。
该问题的解决要感谢同一个 QQ 群班级里的 石巨人
同学(QQ 号开头553,尾号827;)
2. 如何爬取「性别」?(踩坑)
其实这不算坑。只要你看了作业提示,你估计就会想:
为什么要用条件语句 if-else?
我想到了这个疑问,于是我猜想:
或许有些房东与其他房东在「性别」项目上的展示方式不同?
我在小猪短租的北京短租首页点开了 9 位房东。幸运的是,我很快就发现了有的人与其他人不一样:有些人是没有个人页面的,像这位。
观察其地址,发现她多了一个 /no.html
;于是我猜想:
如果对 `http://www.xiaozhu.com/fangdong/707588338/` 发出请求,服务器相应的响应,会不会不同于服务器对于主机对 `http://www.xiaozhu.com/fangdong/1579992935/` 的请求的响应?
于是我使用了 request.get()
请求并打印了 http://www.xiaozhu.com/fangdong/707588338/
对应的 BeautifulSoup 对象,并检查该网页的标题「该房东暂未开通房东专页」对应的代码;同时检查有个人页面的房东页面,例如这个网页的源代码,并查看其「性别」部分的代码,意外发现一个明显的区别:
有个人页面的房东页面中,有且仅有一个 `` 的标签,而无个人页面的房东页面中则无该标签
这意味着我们可通过搜索页面代码中该标签的有无来判断该房东是否有个人页面,从而来决定如何抓取性别。这可以通过 .find_all("tag_name", class_="value")
方法来实现第一次判断:
if 0 == len(profileSoup.find_all("ul", class_="fd_person")):
# processing info about landlord without personal site
else:
# processing info about landlord with personal site
对于有个人页面的房东页面:只要抓取到
并使用- 性别:女
.get_text()
,配合字符串的.strip()
方法把「性别:」去掉,就能得到房东性别。对于没有个人页面的房东页面:观察发现
http://www.xiaozhu.com/fangdong/707588338/
对应的页面中有一句「她的短租房列表:」,这意味着我们只要通过抓取这句话的第一个字,与「她」进行比较,即可得到房东性别。
注意在与「她」比较前,先用 string.encode('utf-8')
把待比较的汉字字符串 string 转为 UTF-8 编码。
另外,建议在程序开始前就定义好常量,以便修改和检查程序,避免出现「魔数」(Magic number)之类的问题。
从而抓取性别的代码大致如下:
# profileURL 中保存了房东个人页面对应的网址
profileGet = requests.get(profileURL)
profileSoup = bs(profileGet.text, 'lxml')
if 0 == len(profileSoup.find_all("ul", class_="fd_person")):
genderStatement = [i.get_text() for i in profileSoup.select('body > div.contentFD.clearfix > div.fd_con > \
div.fd_infor > dl > dt')][0]
if FEMALE_TIP == genderStatement[0].encode('utf-8'):
gender = FEMALE
else:
gender = MALE
3.
方法接受的参数
.select(select-para) select-para
不一定要填入 CSS Selector 的绝对路径,只要填入唯一定位信息即可(例如(部分)相对路径);唯一定位信息的获取,通过观察与分析网页源代码得到(视频+ 踩坑)
-
向
select-para
字符串中的标签名后加入[para-name=value]
,可使
把满足.select() [para-name="value"]
的标签都筛选出来。例如select-para = 'img[width="90"]'
,那么
将筛选出所有满足.select() width-90
的
标签对象(视频)注意:以
'img[width="90"]'
为例,这个字符串中没有任何空格,像'img[width = "90"]'
的写法是错误的!(踩坑)
4. 模拟登录(视频)
打开 Chrome 的 Inspect 选项,切换到 Network 标签页,点击下方左侧栏目中 Name 下任一(?)项,查看右侧栏的 Headers 标签页 Request Headers 中的 Cookie
与 User-Agent
,然后使用如下代码,即可在使用 requests 请求并下载网页时模拟登录:
headerInfo = {
'Cookie': COOKIE_STRING,
'User-Agent': USER-AGENT-STRING
}
requests.get(url, headers=headerInfo)
5. 反反爬虫技巧 001:延时(视频 + 踩坑)
服务器可能通过对发送请求的频率来判断用户是否为爬虫。
我们可以通过在每次 requets.get() 之间插入延时代码来模拟人类:
import time
time.sleep(num)
# 延迟 num 秒;num 可以是浮点数
6. 反反爬虫技巧 002:从移动端代码抓取信息(视频)
有些信息在 PC 端被处理过,例如是通过 JS 代码来加载图片的,这样就可能导致直接抓取
标签可能会得到无效信息。
由于移动端代码相对 PC 端简单,我们可以通过使用 Chrome Inspect 工具栏顶部的 Toggle device mode 模拟移动端加载,从而可获取:
- 模拟移动端的 Cookie 与 User-Agent:用于爬虫中的 header 参数
- 移动端的网页代码:可能更容易找到定位图片的有效信息
7. 连续爬取连续多张网页(视频)
观察连续几张网页的地址变化规律,代入 request.get()
的参数即可