写在前面
之所以会有这篇东西,是因为在爬豆瓣读书的时候一不下心IP被ban了。 - -
不想换自己服务器的IP(学校的IP白嫖一下?),等解封的时候就水以下贴。
预计这篇帖子会不定期地更新,主要说一下关于爬虫的基本知识和例子(主要是水- -)。
相关工具
首先爬虫的话我是用Python码的,用到的模块主要有两个:requests和pyquery ,requests的话主要是通过发送请求来获取网页信息的;pyquery用法和jquery类似,可以用来操作DOM,主要是用来提取数据。
另外数据的处理还用到了json和csv模块,用json是因为网页中爬回来的大多是json格式的,csv是我选择的保存格式(如果要直接存json或者存数据库的话可以忽略这个)。
还有一些通过随机化来破解防爬机制的(我豆瓣IP被ban就是因为没搞这个- -),用到time、random和fake_useragent,time是用来延时一定时间发送,fake_useragent是用来伪造浏览器信息(报头伪造)。
这些模块用pop都能装(应该都会装的吧,就不多说了),但pyquery好像是只能用python3的,所以下面代码都用python3。
几个步骤
- 明确要爬数据的是什么。
- 观察这些数据服务器是怎么传到前端的,大概会在哪些包出现(最好是能直接通过请求来得到这些包,而不是在渲染好的前段页面里面挑出来)。
- 码代码。requests构造请求,pyquery或json挑数据,csv存数据。
- 等。
例子1——中国大学MOOC的课程评论
(拿MOOC做例子的一个原因是它的防爬只需要一点简单的绕过就可以了- -)
先定个小目标试探一下,比如先爬这个课程的某一页评论,然后再到某个课程的全部评论,再到整站的评论信息。
目标定好后,下一步就是观察数据是怎么从后端传送到前端的,这里要借助chrome的开发者工具的Network工具(F12打开开发者工具,有一栏叫Network的,再不清楚可以google一下),首先可以肯定的是这些评论信息是从后端服务器送过来的(这句是废话...),而且是我点击下一页评论时,新的评论信息才会发送过来(因为一次性传所有评论的话会让用户等太长时间了,而且一次也显示不了这么多,最重要的是我看过Elements里面没有)。这样的话只要关注点击下一页之后发送了什么包就好了。
可以看到Network里面有两个数据包和一堆图片,图片其实是头像来的,没什么用,就是说主要有用的是那两个数据包(其实只有第一个有用)。分析一下第一个包的组成,首先是url有两部分,地址和csrfKey参数(不知道url参数的可以google),看参数名和后面一串像哈希值的东西的话可以大概猜到这是个令牌,就是有了它才能访问这个网页。直接把url复制到浏览器的话是不能访问的,会被告知不能用GET方法:
看回Network里面的请求头的话会知道是用POST方法的,但其实在python脚本里直接用POST方法也是不行的会被告知“非法跨域请求”,猜测是识别到了报头中的user-agent和csrfKey对应的有所不同,所以这里要伪造一个user-agent(可以用requests,后面再说)。
通过简单的代码审查可以发现csrfKey其实就是cookie中的NTESSTUDYSI的值(代码中搜一下csrfKey就能找到,不懂cookie的可以google),所以可以通过设置cookie设置csrfKey。
最后还要设置一下表单的参数,在Network中滚到最下面可以看到,可以按名字猜测大概的功能,比如courseId是课程的编号,pageIndex是请求的页码,pageSize是一页中的评论条数(经过测试,这个写0的话是获取全部?),orderBy大概是排序的方法。
知道请求时要发什么参数后接着就是要用requests码代码了,码之前可以先google以下requests的函数原型,要设置表单参数的话需要设置一个叫data的参数,要设置cookie和user-agent的话需要设置一个叫headers的参数,headers部分的东西从浏览器里面copy过去就可以了,下面是代码:
#!/usr/bin/env python3
# -*- coding: UTF-8 -*-
import requests
from pyquery import PyQuery as pq
import os
## headers跟浏览器的设一样(伪造报头)
headers = {
'user-agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Ubuntu Chromium/75.0.3770.90 Chrome/75.0.3770.90 Safari/537.36',
'cookie': 'EDUWEBDEVICE=2a699bca100a4bc6ba3267e6a946c106; WM_TID=282TTKi%2Ft2JFQAFFBEIpiuNVisSn%2B6BL; WM_NI=vfNOutD42da2vwKncHuYD54MKB5cCSyvx%2FNWnk9Dkp0ec%2Fksm1Y0UWJ9rjpAQsqPkjvpv4ynIxTLQpDcXfwcUjGNej1Q9Y4F9DB7wr8hOxjles5oEBQgoU2PBW5dcwtKcEg%3D; WM_NIKE=9ca17ae2e6ffcda170e2e6ee92c421b6edf9a7e6348fb48ea7d54f968e9bbbf372e997b782ca48a393bab1b52af0fea7c3b92ab6ba82b0f88088ecfe85e43ab6a9858fe244b7878cd6b747899efb84c259bbaab8d0cd5daeb5f7a5ea7cf4bbadd0ea79afef97d1d85d8ae7be8bf34b93adbfb1ea62b6b0bad5fc668ba89cadd47c88bd9887f66997b689afe862f38ff7bab74efb9c8ed8f67afcb88199e968a7adfb90c84a93b9a1b6b15c8289fb85e27db895ada6e637e2a3; NTESSTUDYSI=506623b8face4ca29ef722e581bd3227; utm="eyJjIjoiIiwiY3QiOiIiLCJpIjoiIiwibSI6IiIsInMiOiIiLCJ0IjoiIn0=|aHR0cHM6Ly93d3cuZ29vZ2xlLmNvLmpwLw=="; hb_MA-A976-948FFA05E931_source=www.google.co.jp; Hm_lvt_77dc9a9d49448cf5e629e5bebaa5500b=1562574902,1562585346,1562653862,1562677890; __utma=63145271.884186124.1562574903.1562662301.1562677890.6; __utmc=63145271; __utmz=63145271.1562677890.6.4.utmcsr=google|utmccn=(organic)|utmcmd=organic|utmctr=(not%20provided); Hm_lpvt_77dc9a9d49448cf5e629e5bebaa5500b=1562678566; __utmb=63145271.12.9.1562678784603'
}
url = 'https://www.icourse163.org/web/j/mocCourseV2RpcBean.getCourseEvaluatePaginationByCourseIdOrTermId.rpc'
## csrfKey从浏览器拿,作用相当于SessionId
## courseid就是课程号,可用于遍历
## 剩下是页数之类的
data = {
'csrfKey':'506623b8face4ca29ef722e581bd3227',
'courseId':'93001',
'pageIndex':'1',
'pageSize':'20',
'orderBy':'3'
}
resp = requests.post(url,data=data,headers=headers)
doc = pq(resp.content)
text = doc.text().encode('latin1').decode('utf-8')
print(text)
最后结果得到的是一个json,包括data里面指明的courseId那门课的第pageIndex页的pageSize那么多条数据,通过改变这几个参数就可以获取特定的评论数据。
(代码直接复制不知道还能不能用,它的令牌刷新周期好像是挺长的- -)
写在后面
(剩下的有时间再写shui)
(PS:豆瓣的防爬还没有很好的方法解决,知道怎么搞的dalao给我支支招阿?)