Web_Spider_3

网络爬虫入门教程(3)

这篇博客将继续介绍爬虫程序中模拟登陆的解决方法、post方式和信息提取的方法。

使用post方式进行模拟登陆

在之前的博客中所介绍的爬虫都是以get的方式来访问服务器的,这篇博客将讲述post方式的基本用法。

前一篇博客引出了登陆问题,(这里解释一下)由于HTTP协议是无状态的(下一次请求并不知道上一次请求的信息),所以登陆之后服务器与浏览器之间进行的各种请求与响应需要确定用户是否登陆,自然而然我们需要一个标志,而cookie就充当了这样一个角色。所以我们要想解决登陆问题,就必须想办法获取cookie,上一篇博客中采用抓包是一种方式,但是显然不是我们想要的,下面就给出纯代码模拟登陆的方法:

在这个代码中,我们要爬取人人网个人页面的html文件,显然需要先进行登陆。在写代码前我们需要用fiddler抓取登陆时的包,找到host的URL和表单数据,为post请求做准备,为解决cookie问题,我们要引入cookiejar这个模块,用cookiejar创建handler再用handler创建opener,此后的请求都使用opener,cookiejar对象在登陆成功后会自动保存cookie

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35

import urllib.request
import urllib.parse
import http.cookiejar
#创建一个cookiejar对象
cj=http.cookiejar.CookieJar()
#通过cookiejar对象创建一个handler
handler=urllib.request.HTTPCookieProcessor(cj)
#根据handler创建一个opener
opener=urllib.request.build_opener(handler)
post_url="http://www.renren.com/ajaxLogin/login?1=1&uniqueTimestamp=2019822154521"
#这是表单数据,从fiddler中复制过来,改成字典格式
form_data={
'email':'18258317595',
'icode':'',
'origURL':'http://www.renren.com/home',
'domain':'renren.com',
'key_id':'1',
'captcha_type':'web_login',
'password':'567e34db122fc42aa42d8fbe6b76f32beee819ea7aa6639d1e5994ef6c2f85e3',
'rkey':'88c3ce72a86f84868e5006432a0566bc',
'f':'http%3A%2F%2Fwww.renren.com%2F971983124%2Fnewsfeed%2Fphoto',
}
headers={
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.102 Safari/537.36 Edge/18.18362',
}
request=urllib.request.Request(url=post_url,headers=headers)
form_data=urllib.parse.urlencode(form_data).encode()
response=opener.open(request,form_data)
print(response.read().decode())
#登陆后再用get请求获取HTML文件
get_url="http://www.renren.com/971983124/profile"
request=urllib.request.Request(get_url,headers=headers)
response=opener.open(request)
print(response.read().decode())

post请求的一般格式

post请求的格式和get很相似,不同之处在于:get请求传输的数据是querystring,通过拼接的方式附在URL后面,而post传输数据则以表单的方式,相对安全性要高。在编写代码时,get方式要注意参数的拼接,而post方式要以字典的形式把formdata数据传递给服务器。下面再给出一个post方式的例子:

这个例子模拟用户使用百度翻译,获取搜索结果(由于百度采取了加密,所以当我们更改搜索的单词时,爬虫程序会失效,想要破解需要分析js代码,找出加密的方式,理论可行,实际非常困难)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
import urllib.request
import urllib.parse
word='baby'
post_url="https://fanyi.baidu.com/v2transapi"
headers={
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.102 Safari/537.36 Edge/18.18362',
# 'Accept': '*/*',
# 'Accept-Language': 'zh-Hans-CN,zh-Hans;q=0.5',
# 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8',
# 'X-Requested-With': 'XMLHttpRequest',
# 'Accept-Encoding': 'gzip, deflate, br',
# 'Host': 'fanyi.baidu.com',
# 'Content-Length': '121',
# 'Connection': 'Keep-Alive',
# 'Cache-Control': 'no-cache',
'Cookie': 'BDRCVFR[k2U9xfnuVt6]=mk3SLVN4HKm; H_PS_PSSID=; delPer=0; PSINO=1; BAIDUID=B25FFDAC5A102DA419F1283CC7BE770D:FG=1; BIDUPSID=B25FFDAC5A102DA419F1283CC7BE770D; PSTM=1567391659; BDORZ=FFFB88E999055A3F8A630C64834BD6D0; yjs_js_security_passport=9dad9c7e98f7c96362e0901326330787f15c901f_1567511321_js; BDUSS=gzQmJoTUlJMTJ4NjRLS0IzdGNPOWZRcGNrT1F-U1dEOFI1N0JvczlnbmJrWlJkRVFBQUFBJCQAAAAAAAAAAAEAAADg1FhMt-e5~c7eutt6bQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAANsEbV3bBG1dN2; __yjsv5_shitong=1.0_7_77bc4d143bb6e9e9889cea03584640024746_300_1567511320184_60.247.46.118_8ba4d331; locale=zh; Hm_lpvt_64ecd82404c51e03dc91cb9e8c025574=1567511320; from_lang_often=%5B%7B%22value%22%3A%22zh%22%2C%22text%22%3A%22%u4E2D%u6587%22%7D%2C%7B%22value%22%3A%22en%22%2C%22text%22%3A%22%u82F1%u8BED%22%7D%5D; SOUND_PREFER_SWITCH=1; APPGUIDE_8_0_0=1; to_lang_often=%5B%7B%22value%22%3A%22en%22%2C%22text%22%3A%22%u82F1%u8BED%22%7D%2C%7B%22value%22%3A%22zh%22%2C%22text%22%3A%22%u4E2D%u6587%22%7D%5D; FANYI_WORD_SWITCH=1; Hm_lvt_64ecd82404c51e03dc91cb9e8c025574=1567511320; SOUND_SPD_SWITCH=1; REALTIME_TRANS_SWITCH=1; HISTORY_SWITCH=1',
}
form_data={
'from':'en',
'to':'zh',
'query':word,
'transtype':'realtime',
'simple_means_flag':'3',
'sign':'814534.560887',
'token':'4b7ec249cc1766cf4cb6d26480abbce4',
}
form_data=urllib.parse.urlencode(form_data).encode()
request=urllib.request.Request(post_url,headers=headers)
response=urllib.request.urlopen(request,form_data)
print(response.read().decode())

post方式的表单数据无法在浏览器找到,必须进行抓包来获取。在这个例子中,我们在headers中仅仅模拟User-agent这一项无法获取到html,此时我们应该把header中的内容全部复制过来(Accept-Encoding除外,加入这一项则服务器返回的资源进行了压缩,而我们无法用程序进行解压缩,所以不能加入这一项),当然并不是所有的项都起作用。在这个例子中,经过检验可知header中只有UA和cookie起作用。

页面数据提取的三种方式

1.正则表达式(基于python,其他语言或有不同)

  • 匹配单个字符
\d 匹配一个数字(0-9)
\w 匹配一个字母或数字(0-9 或 a-z 或 A-Z)
. 匹配除换行以为所有单字符
[0-9] 匹配一个0-9的数字相当于\d,非固定形式,也可以[3-7]等等,下同
[a-z] 匹配一个小写英文字符
[A-Z] 匹配一个大写英文字符
[0-9a-z]等 上述的组合,比如这个式子匹配一个数字或小写英文字符,还可以有其他组合
\n 匹配一个换行符
\r 匹配一个回车符
\t 匹配一个制表符
\v 匹配一个垂直制表符(很少用)
  • 匹配多个字符
\s 匹配任何空白字符(亲测python包括空格、换行符、制表符、回车符)
\S 匹配任何非空白字符
  • 匹配数量限定符
* 匹配前面的表达式零次或多次
+ 匹配前面的表达式一次或多次
匹配前面的表达式零次或一次
{n} 写在表达式后面,n是一个整数,表示匹配n次(不能多也不能少)。如r”o{p}”可以匹配apple中的“pp”而不能匹配person中的“p”
{n,} 写在表达式后面,n是一个整数,表示至少匹配n次(注意,没有{,n}这种形式
{n,m} 写在表达式后面,n,m是一个整数,表示至少匹配n次,至多匹配m次
  • 特殊符号
$ 用在表达式开头,限定从字符串结尾开始匹配,如r”$(app\w*?)”不能匹配“application”,但是能匹配“wapp”
^ 用在表达式开头,限定从字符串开头开始匹配
\b 匹配处于边界的字符串,即从开头或结尾开始匹配
\B 匹配非边界字符串,即匹配处于中间的字符串
() 括号内的表达式所匹配的字符将是你想要获取的,可使用多个括号,但不能嵌套,有多个括号时用\n(n为括号的标号,n从0开始)代表第n+1个括号。如r”ab(\w)oul(\d+)ge\1”中\1代表再次匹配第二个括号,且调用方法如re.findall时返回的将是括号内匹配到的内容,单个括号返回字符,多个括号返回列表
限定非贪婪模式,即匹配?前面的表达式时,尽量匹配最少的字符。如当r”\d+”会匹配”23232dfe”中的”23232”,而r”\d+?”只会匹配最少的“2”,因为+限定了最少匹配一个
\ 逻辑或,\ 两边的表达式,只要满足一个就会匹配成功,可使用多次
\ 取消转义,若要匹配上述表格中的符号需要在前面使用\取消转义,如r”\?”匹配“?”字符
  • 尾部的修饰符
re.I 大小写不敏感,即匹配时不区分大小写
re.M 多行匹配,作用于^和$。正常模式下r” ^she$”只会匹配第一行中的she,而指定re.M则会匹配每一行中的she
re.S 使得.可以匹配包括\n在内的所有字符

下面是爬虫常用的一些正则表达式:

  1. Email地址:^\w+([-+.]\w+)@\w+([-.]\w+).\w+([-.]\w+)*$

  2. 域名:[a-zA-Z0-9][-a-zA-Z0-9]{0,62}(/.[a-zA-Z0-9][-a-zA-Z0-9]{0,62})+/.?

  3. Internet URL:[a-zA-z]+://[^\s] 或 ^http://([\w-]+\.)+[\w-]+(/[\w-./?%&=])?$

  4. html标签:

    • 单标签

      (<.*?>)

    • 双标签

      r”(<\w+?.?>.?</.*?>)”

2. xpath

xpath是基于树结构来查找标签元素的,因此用来解析HTML非常方便,下面给出xpath的语法

// 选取当前的所有节点(标配的开头)
. 选取当前节点(二次Xpath提取时会使用)
.. 选取当前节点父节点
@ 选取属性
* 匹配任何元素节点
@* 选取所有有属性的节点
/ 从根节点选取,即选取根节点下的元素,如//div/span选取div下的span
label[@xxx=”###”] 选取某个有属性xxx,且属性值为###的标签
contains(@property,’key’) 选取有property属性且属性值包含key的元素
starts-with(@property,’key’) 选取有property属性且属性值开头为key的元素
div[text()=’key’] 选取内容为key的div(可换为其他标签)
/text() 获取某个元素的文本内容,即html标签之间的内容
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
</head>
<body>
<div class="content">
<span id="button">
<p link="www.baidu.com">
<div class="sp">7</div>
<div class="main" name="hello">65</div>
<div name="bjiu">7467</div>
</p>
</span>
</div>

</body>
</html>

例如要选择最内层第三个div,xpath可写为:

\\div[@class=”content”]\\p[contains(@link,”baidu”)]\div[2]

当xpath匹配的不是一个元素而是多个元素时,可以像数组一样使用下标来获取其中的元素

xpath选取出来的元素可以继续用xpath来提取内容,所以当难以一次性的xpath比较难写时,或者要提取的内容处于不同位置单其父级具有相同的结构时可以考虑先定位到父元素,再提取最终想要的内容。

3. beautifulsoup

首先要构建一个beautifulsoup的对象

1
soup = BeautifulSoup(open('xxx.html', encoding='utf8'), 'lxml')
  • 可以以”.”来访问某个元素的子元素;可以使用字典索引的方式访问元素的属性值

    如soup.a[‘herf’]表示提取整个html中a标签的链接

  • 可以通过“.text”或者”get_text()”或者”.string”获取标签中的文本

    如soup.div.text/.string/.get_text()表示提取div中的文本。

    .text和.string有一些区别,详细请参考https://blog.csdn.net/zqxnum1/article/details/84587357这篇博客

  • find(label,property=value),选取有属性property且值为value的label,找到一个就返回

    find_all(label,property=value),同上,但是返回一个列表,可通过索引访问其中元素

    如:soup.find_all(‘span’,’class’=’gave’)选取所有class=gave的span标签

  • select(‘标签选择器’)

    select里面写css中的标签选择器

    如:select(div>span>ul)表示选取div下的span下的ul

    ​ select(.book>span>div)表示选取class=book的元素下的span下的ul

如想了解更多用法,右转官方文档$\rightarrow$https://beautifulsoup.readthedocs.io/zh_CN/latest/

文章目录
  1. 1. 网络爬虫入门教程(3)
    1. 1.0.0.1. 使用post方式进行模拟登陆
    2. 1.0.0.2. post请求的一般格式
  • 1.1. 页面数据提取的三种方式
    1. 1.1.0.1. 1.正则表达式(基于python,其他语言或有不同)
    2. 1.1.0.2. 2. xpath
    3. 1.1.0.3. 3. beautifulsoup