码以致用02 - 用 Scrapy 爬虫抓取简单心理咨询师资料

目标和步骤

爬虫目标:从简单心理网站上抓取心理咨询师列表和信息。

学习目标:

  • 熟悉 Scrapy 框架,理解如何使用
  • 初步掌握 xpath 语法
  • 导出爬取信息为 csv
  • 用 Pandas 查看和清理数据

简单心理网站上有「咨询咨询」和「精神科顾问」两类专家,这里先尝试抓取咨询师资料。

咨询师展示列表比较简单,一共有 49 页,每页有 10 或 11 个咨询师(真是有点坑……)。抓取每页上的信息即可。

步骤分解:

  • 抓取每一页上面所有咨询师的信息,包括姓名、简介、地点、咨询方式、地点、价格等
  • 按页面顺序抓取全部咨询师资料
  • 导出信息为 csv
  • 用 pandas 查看信息

新建项目和爬虫

上一篇已经介绍过 Scrapy 爬虫框架和如何新建 Python 虚拟环境。现在来新建一个 Scrapy 爬虫项目:

1
scrapy startproject jdxl

Scrapy 生成了以下文件

1
2
3
4
5
6
7
8
9
├── jdxl
│   ├── __init__.py
│   ├── items.py
│   ├── middlewares.py
│   ├── pipelines.py
│   ├── settings.py
│   └── spiders
│   └── __init__.py
└── scrapy.cfg

我们在 spiders 文件夹里新建爬虫文件 counselor.py

然后在 items.py 里面定义要抓取的项目:

1
2
3
4
5
6
7
8
9
class JdxlItem(scrapy.Item):
# define the fields for your item here like:
# name = scrapy.Field()
name = scrapy.Field() #姓名
url = scrapy.Field() #链接
info = scrapy.Field() #简介
zx_type = scrapy.Field() #咨询类型
location = scrapy.Field() #地点
price = scrapy.Field() #价格

抓取页面信息

打开爬虫 counselor.py,开始写爬取的程序。

不要忘记先 import 上面定义好对象:

1
from jdxl.items import JdxlItem

问题1:如何设置起始 URL?

打开心理咨询师列表页面,然后翻到第二页,发现 url 是很长的一串:

1
https://www.jiandanxinli.com/experts?filter%5Bcity_id%5D=&filter%5Bfield_id%5D=&filter%5Bgender%5D=&filter%5Bonly_available%5D=&filter%5Bonly_junior%5D=&filter%5Bonly_online%5D=&filter%5Bprice%5D=&filter%5Bq%5D=&filter%5Bsect_id%5D=&filter%5Btarget_id%5D=&filter%5Btime%5D=&filter%5Btype_id%5D=&page=2

中间都是传递的筛选参数,只有最后 &page=2 才是关键。也就是说抓取的页面URL是这样的:

1
2
3
4
https://www.jiandanxinli.com/experts?&page=1
https://www.jiandanxinli.com/experts?&page=2
...
https://www.jiandanxinli.com/experts?&page=49

class JdxlSpider(scrapy.Spider): 下面开始定义起始 URL:

1
2
3
4
5
6
7
8
allowed_domains = ["jiandanxinli.com"]
start_urls = ['http://jiandanxinli.com/experts']
start_url_list = []

for i in range(1,50):
start_url_list.extend(['http://jiandanxinli.com/experts?&page=' + str(i)])

start_urls = start_url_list

问题2:如何抓取一个节点的信息

def parse(self, response): 函数中定义要抓取内容,用 XPath 语法告诉爬虫要抓取的节点位置。

什么是「叉怕死」呢?

XPath (XML Path Language) is a query language for selecting nodes from an XML document. In addition, XPath may be used to compute values (e.g., strings, numbers, or Boolean values) from the content of an XML document. —— Wiki

那怎么写 XPath 呢?

感谢 Chrome,直接提供了 XPath 选取功能。对需要抓取的位置单击右键,点击 Inspect,打开 chrome-devtools 面板:

对准要抓取的节点,再次右键,点击 Copy XPath,XPath 路径就复制好了。

别高兴得太早,在调试坑里跌倒无数次的 00 颤抖地告诉你:直接复制的 XPath 往往不能直接用……

比如上面的咨询师姓名这里,chrome 提供的路径是:

1
//*[@id="content_wrapper"]/div[2]/div[2]/a[5]/div[1]/strong/text()

但更准确的路径是:

1
/div[@class="summary"]/strong/text()

.

对初学者来说,如果之前没有太多写 html 和 css 的经验,每一个 xpath 都需要摸索好半天。不过这也是必经之路吧。折腾多了,就学会老老实实去看 XPath 的文档了。

问题3:内容没有节点怎么办?

抓到咨询师的咨询方式、地点、价格等信息的时候,坑爹的事情来了。

这一坨的结构是:

文字竟然没有包括在标签之内!前后都是个 i 标签!要怎么抓!

然后开始了漫漫 Google 之路。最后终于找到了这篇:如何用scrapy提取不在标签内的文字?

following::text() 的方式抓取了几个信息:

1
2
3
zx_type = response.xpath('./div[@class="info"]/i/following::text()').extract()[1]
location = response.xpath('./div[@class="info"]/i/following::text()').extract()[2]
price = response.xpath('./div[@class="info"]/i/following::text()').extract()[3]

这样必需是每一栏信息都没有缺少,否则抓取就会错位……暂时这么处理吧 >.<

问题4:如何抓取多个专家信息

抓取好一个专家的信息后,要怎么把每个页面 10~11 个专家的信息都抓下来呢?看了页面的 html,每个专家都在 <a class="expert" ...> 标签下面。于是用循环获取所有带有这个特征的标签。

为了缩小范围,在 response.xpath('//a[@class="expert"]') 就传入了父节点的路径。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
for each in response.xpath('//a[@class="expert"]'):
print(each)
item = JdxlItem()
# 抓取姓名
item['name'] = each.xpath('./div[@class="summary"]/strong/text()').extract()
# 抓取 url
item['url'] = each.xpath('./@href').extract()
# 抓取简介
item['info'] = each.xpath('./div[@class="summary"]//div[@class="content"]/text()').extract()
# 抓取咨询方式、地点、价格等
item['zx_type'] = each.xpath('./div[@class="info"]/i/following::text()').extract()[1]
item['location'] = each.xpath('./div[@class="info"]/i/following::text()').extract()[2]
item['price'] = each.xpath('./div[@class="info"]/i/following::text()').extract()[3]

yield item

其他配置

settings.py 文件里添加模拟 user_agent 的模块(需要先 pip 安装 faker 包)、设置爬取间隔、头信息等:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
from faker import Factory
f = Factory.create()
USER_AGENT = f.user_agent()

# Obey robots.txt rules
ROBOTSTXT_OBEY = True

DOWNLOAD_DELAY = 1

DEFAULT_REQUEST_HEADERS = {
'Host': 'www.jiandanxinli.com',
'Accept': '*/*',
'Accept-Encoding': 'gzip, deflate, br',
'Accept-Language': 'zh-CN,zh;q=0.8',
'Cache-Control': 'no-cache',
'Connection': 'Keep-Alive',
}

测试和抓取

开始抓取的命令是:

1
scrapy crawl counselor

crawl 后面跟的是在 class JdxlSpider(scrapy.Spider): 中定义的爬虫名字。

先抓取 2 页试试:

1
2
for i in range(1,3):
start_url_list.extend(['http://jiandanxinli.com/experts?&page=' + str(i)])

调试过程主要问题是节点的 xpath 提供得不准确,没有抓取到内容。另外就是可能忘记在 items.py 里面设置 item。一般来说,根据报错慢慢找,总能找出问题,耐心一些就是了。

输出 csv

查看了官方文档里面有关输出的部分 Feed exports — Scrapy 1.4.0 documentationItem Exporters — Scrapy 1.4.0 documentation,试了一下写 pipelines,有点复杂,没有成功。

然后搜到 Scrapy爬虫框架教程(二)– 爬取豆瓣电影TOP250,只需要在执行爬虫时设置输出参数就可以了:

1
scrapy crawl counselor -o output_file.csv

查看、清理数据

新建 Jupyter Notebook,import pandas 包,用 pd.read_csv 命令查看文件:

抓取的链接不完整,补全并输出:

1
2
df['url'] = 'http://jiandanxinli.com'+df['url']
df.to_csv('counselor.csv', index=False)

下一篇继续介绍用 Pandas 和 Bokeh 做简单的数据统计和可视化。

项目源码请查看 00 的 github repo

Ref

kidult00 wechat
扫码关注 00 的公众号
如果文章帮您节省时间或者解答疑问,不妨打个赏 :)