先说结果
爬取了🦉猫途鹰网站上34,237个景点的所有中文评论共25,165条
😝基本上我敢肯定景点一定是很全很全的(费了老大劲的);
☹️评论我查了几个景点,基本也都是全的. 但是我没有IP池,所以后来被反爬了😭但我觉得代码是没问题的,理论上说能爬到所有中文评论
源代码请见 OliveDS-GitHub
访问我的数据库
- IP: 47.93.238.102
- Port: 3306
- MySQL账号: dsjk
MySQL密码: dsjk
Database: scrapy_sies
- Table: sites_all_url_copy4 (中国所有景点)
- Table: sites_all_review (所有中文景点评论)
网站分析
本来以为从 中国
->景点
这个页面
https://www.tripadvisor.cn/Attractions-g294211-Activities-China.html
下应该能找到一个总表,直接爬取即可,但是看了一下才发现并没有这样一个表,而且给出的分类数据十分不全,总共才296个景点(应该都是精华景点吧…)所以肯定不能想着一次把所有景点爬全了
所以我打算采用的方案是,先从中国
->目的地
页面
https://www.tripadvisor.cn/Tourism-g294211-China-Vacations.html
把中国所有目的地(按排行)爬下来,再按照目的地把当地所有景点爬取下来,以青岛为例,就是其这个页面https://www.tripadvisor.cn/Attractions-g297458-Activities-Qingdao_Shandong.html#ATTRACTION_SORT_WRAPPER(同样是按排行)
推测这样应该能爬到比较完整的数据了
爬取所有目的地链接
依然是这个链接
https://www.tripadvisor.cn/TourismChildrenAjax?geo=294211&offset=11&desktop=true
在受欢迎的目的地
标签下有个中国的热门目的地
排行榜,虽然榜单只显示了部分内容,但可以点击查看更多中国热门目的地
加载,我试了一下,貌似很多很多页,能一直加载下去,目测是包含了所有目的地的
打开Chrome的开发者工具
,点击查看更多中国热门目的地
查看页面发送的Request,发现是通过这个请求中的offset
值在加载新的内容的
https://www.tripadvisor.cn/TourismChildrenAjax?geo=294211&offset=1&desktop=true
用浏览器直接打开这个链接,可以发现,offset
为1时,加载的就是第七名之后的目的地,所以修改offset就可以获得所有目的地,而1~6在原网页
我们的任务就是首先爬取所有的中国目的地(的链接),然后再爬目的地页面的景点
动手
安装Scrapy
(已经安装了Python3 & pip3)
1 | pip3 install scrapy |
安装完成后可以在所需位置直接
1 | scrapy startproject maotuying |
新建工程
配置items
在maotuying
->maotuying
->items.py
中配置需要通过Pipeline存储的内容(即爬虫获取的对象)
先考虑做第一级爬虫(目的地HTML),所以
1 | class MaotuyingItem(scrapy.Item): |
创建爬虫
1 | scrapy genspider sites www.tripadvisor.cn |
deshtmls
是爬虫任务的名称,www.tripadvisor.cn
是domain name
通常domain.name都是不带
www
的,但是猫途鹰网站的都带,查阅一下论坛发现The “www.” convention is redundant, old fashioned and … (IMO) … ugly. Most places who use “www.example.com" will also have a server at “example.com”, and one will redirect to the other as appropriate.
果然尝试一下在浏览器输入
http://tripadvisor.cn
会直接跳转成https://www.tripadvisor.cn
另: https的s代表
secure
,是使用SSL加密传输的HTTP,更加安全(同样的,使用https的网站会自动将http
协议更正为https
)
建立爬虫后,打开sites.py
文件,将start_urls
默认为http://www.tripadvisor.cn
修改成了
1 | class DeshtmlsSpider(scrapy.Spider): |
检查所需页面元素
使用Chrome
的Developer Tools
尝试爬取
尝试爬取后发现response为空,使用scrapy shell进行调试,发现
1 | scrapy shell 'https://www.tripadvisor.cn/Tourism-g294211-China-Vacations.html' --nolog |
找到一个解答:
https://stackoverflow.com/questions/43973898/scrapy-shell-return-without-response
将setting.py中修改为
1 | ROBOTSTXT_OBEY=False |
修改后,可以在shell 爬取的界面中
1 | In [4]: response.xpath('//div[@class="popularCities"]/a/@href').extract() |
正式爬取
爬取逻辑
- 爬取中国所有景点的URL,并存储到MySQL数据库中
- 依次爬取所有景点的中文评论
爬取中国所有景点的URL
具体又可以分为:
start_requests
方法构造初始请求(目的地)的request需要设置
{offset}
值请求所有目的地网页parse_des
方法解析目的地的URLparse_sites_firstpage
方法解析目的地的景点列表的页数,并构造下一步请求景点列表的URL同样需要设置
{pages}
值parse_sites_1page
解析景点的名称和URL,存入MySQL
代码如下:
1 | # -*- coding: utf-8 -*- |
依次爬取所有景点的中文评论
具体步骤又为:
start_requests
方法从MySQL中取出景点的URL,构造初始请求parse_sites_firstpage
解析景点的评论页数,,并构造下一步请求评论页面的URL同样需要配置URL中的
{pages}
值parse_site_reviews
方法解析评论的详细内容,并存储到数据库中
代码如下:
1 | # -*- coding: utf-8 -*- |
连接MySQL
参考这篇教程
https://www.accordbox.com/blog/scrapy-tutorial-9-how-use-scrapy-item/
注意还需要
1 | pip3 install mysqlclient |
才能执行db_connect()
其中models.py
的内容中的QuoteDB
需要修改为自己的DB,然后测试代码中的DB名称和字段也需要根据自己的DB修改. In my case 测试代码和结果为
1 | from sqlalchemy.orm import sessionmaker |
在执行这段测试程序时,出现了lib ImportError
,
1 | ImportError: dlopen(/usr/local/lib/python3.7/site-packages/MySQLdb/_mysql.cpython-37m-darwin.so, 2): Library not loaded: libcrypto.1.0.0.dylib |
1 | sudo ln -s /usr/local/mysql-8.0.15-macos10.14-x86_64/lib/libcrypto.1.0.0.dylib /usr/local/lib/libcrypto.1.0.0.dylib |
我的解决方案是把需要的lib逐个link到了/usr/local/lib/
下. 如果有更好的解决方案,please let me know
也可以使用张老师课件上pymysql
插件进行连接(需要自己创建数据库表和编写insert语句,稍微麻烦一点)
创建数据库可以使用以下语句(示例)
1 | create table sites_trial( |
最终,我的items.py
文件如下:
1 | # -*- coding: utf-8 -*- |
pipelines.py
文件如下:
1 | # -*- coding: utf-8 -*- |
MySQL数据库服务器
阿里云ECS服务器
因为需要将数据库提供给其他同学访问,需要搭建一个MySQL服务器,我用学生优惠开了一台9.5元/月的ECS云服务器,通过其公网IP,可以远程访问
1 | sudo ssh 47.93.238.102 |
ECS刚拿到后需要
1 | sudo apt-get update |
安装MySQL-Server我根据阿里云官方的教程遇到了很多错误,反而后来按照
官方: https://yq.aliyun.com/articles/654980
1 | sudo apt-get install mysql-server |
配置安全组规则
需要给ECS配置允许公网访问其3306端口,如下图所示
数据库迁移
一开始我把数据都存在了本地的MySQL中,所以需要迁移上ECS
将Database存为 .sql文件
1 | mysqldump -u dsjk -p scrapy_sies>scrapy_sies.sql |
将文件上传到ECS
1 | scp /Users/oliveds/Documents/web/maotuying/scrapy_sies.sql root@47.93.238.102:/home/oliveds/ |
这个代码在本机执行,前面是源IP
(省略)源路径+文件名
后面是目标IP
(即ECS的IP) 目标路径
果然可以在我的ECS命令行中查找到它了
1 | root@...:/home/oliveds# ls |
ECS中恢复Database
1 | create database scrapy_sies; # 创建原名称的数据库 |
配置MySQL数据库可被远程访问
创建新用户
首先创建一个新用户,提供给同学和老师们使用
1 | mysql> create user dsjk identified by 'dsjk'; |
第一个dsjk
为用户名,第二个为密码
配置用户权限
1 | mysql> grant all privileges on scrapy_sies.* to dsjk@'%'; |
这里all
表示赋予了所有权限,包括SELECT
,INSERT
,UPDATE`
等,也可以只赋予部分
scrapy_sies
是database的名称,这里只赋予了此database下所有table(.*
表示所有),也可以使用*.*
赋予该用户访问所有database的权限,
@'%'
中的%
表示dsjk可以在任何ip访问,如果想要限制只有某个ip可以访问,将'%'
换成那个ip地址(xxx.xxx.xxx.xxx or localhost)就可以了
1 | vim /etc/mysql/mysql.conf.d/mysqld.cnf |
然后重启一下ECS机器
远程访问-Navicat
使用Navicat
访问远程的数据库比较方便,安装Navicat for MySQL
,新建连接,输入ip(也就是MySQL安装机器的IP地址,可以通过ifconfig
查看),port(通常是3306),刚才创建的可从任意ip访问的新用户(dsjk)的用户名和密码,即可建立连接
Open Connection
后,可以看到授权了该用户访问的scrapy_sies
database中的内容
SideNote
重复页面爬取
我的程序需要先对目的地页面中的页码页码进行提取(解析目的地的景点首页),然后再解析目的地景点的每个页面,这样就造成目的地的景点首页被scrapy解析了两次,而scrapy是默认去重的,所以我一直没有爬到每个目的地首页的30个景点😭
这个问题困扰了一整天,因为问题实在不太好找,我又这么混乱,不熟悉,为了找这个bug我凌晨3点才睡…然并卵😡后来才发现是因为被去重了,好蠢😞
1 | custom_settings = { |
如上,配置其可以重复爬取
Chrome读取缓存
被反爬虫了,但是code还要继续写,这时候访问不了网站了,于是只能先看缓存的内容了
访问chrome://chrome-urls/
,选择chrome://cache
,就可以找之前缓存的页面了
Spider’s Custom Settings 妙用
针对不同的spider有不同配置时,分别写在其custom_settings
中,而不是写在project
->settings.py
,可以避免相互影响,比如两者使用不同Pipeline时,可以
1 | custom_settings = { |
IP代理
虽然通过 设置request Header
请求头能够在很大程度上防止被反爬,但是数据量太大的时候,依然很容易会被禁
这时候如果能有一个IP池来不断换IP地址就更加高枕无忧了.当然,免费的IP池里能用的不多,最好还是购买一个IP代理
配置方法见这个教程:
https://blog.csdn.net/u010978757/article/details/83409571
结果展示
Table: sites_all_url_copy4 (中国所有景点)
Table: sites_all_review (所有中文景点评论)