scrapy爬虫笔记1

文章目录[x]
  1. 1:正则表达式的规则:
  2. 1.1:我们可以使用Python的re模块来使用正则表达式:
  3. 1.2:re 模块的一般使用步骤如下:
  4. 1.3:compile函数:
  5. 1.4:search 方法
  6. 1.5:findall 方法
  7. 1.6:finditer 方法
  8. 2:split 方法
  9. 2.1:sub 方法
  10. 2.2:匹配中文
  11. 3:注意:贪婪模式与非贪婪模式
  12. 3.1:示例一 : 源字符串:abbbc
  13. 3.2:示例二 : 源字符串:aa<div>test1</div>bb<div>test2</div>cc
  14. 4:使用XPath
  15. 5:lxml库
  16. 6:那么,我们现在直接开始实战:

requests库可以添加查询参数,下面是requests库的一些常用的用法:

import requests

kw = {'wd':'长城'}

headers = {"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.99 Safari/537.36"}

# params 接收一个字典或者字符串的查询参数,字典类型自动转换为url编码,不需要urlencode()
response = requests.get("http://www.baidu.com/s?", params = kw, headers = headers)

# 查看响应内容,response.text 返回的是Unicode格式的数据
print(response.text)

# 查看响应内容,response.content返回的字节流数据
print(response.content)

# 查看完整url地址
print(response.url)
# 查看响应头部字符编码
print(response.encoding) 

# 查看响应码
print(response.status_code) 
  • 使用response.text 时,Requests 会基于 HTTP 响应的文本编码自动解码响应内容,大多数 Unicode 字符集都能被无缝地解码。

  • 使用response.content 时,返回的是服务器响应数据的原始二进制字节流,可以用来保存图片等二进制文件。

requests的私密代理使用方法

import requests

# 如果代理需要使用HTTP Basic Auth,可以使用下面这种格式:
proxy = { "http": "mr_mao_hacker:sffqry9r@61.158.163.130:16816" }

response = requests.get("http://www.baidu.com", proxies = proxy)

print(response.text)

web客户端验证

如果是Web客户端验证,需要添加 auth = (账户名, 密码) 这个东西是某些网站的一种认证登录形式,这个不是我们想的账号密码登录。

#web客户端验证
import requests

auth=('test', '12345678')

response = requests.post('网址',auth = auth)

print(response.text)

我们也可以得到网站的cookie数据,cookiejar = response.cookies

我们还可以通过session来登录那些需要登录的网站,虽然我们可以直接通过发送cookie来实现,但是这个比我们直接发更简单:

import requests

# 1. 创建session对象,可以保存Cookie值
ssion = requests.session()

# 2. 处理 headers
headers = {"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.99 Safari/537.36"}

# 3. 需要登录的用户名和密码
data = {"email":"mr_mao_hacker@163.com", "password":"alarmchime"}  

# 4. 发送附带用户名和密码的请求,并获取登录后的Cookie值,保存在ssion里
ssion.post("http://www.renren.com/PLogin.do", data = data)

# 5. ssion包含用户登录后的Cookie值,可以直接访问那些登录后才可以访问的页面
response = ssion.get("http://www.renren.com/410043129/profile")

# 6. 打印响应内容
print(response.text)

跳过SSL验证:r = requests.get("https://www.12306.cn/mormhweb/", verify = False)

数据的处理:

正则表达式的规则:

我们可以使用Python的re模块来使用正则表达式:

有一点需要特别注意的是,正则表达式使用 对特殊字符进行转义,所以如果我们要使用原始字符串,只需加一个 r 前缀,示例:r'chuanzhiboke\t\.\tpython'

re 模块的一般使用步骤如下:

  1. 使用 compile() 函数将正则表达式的字符串形式编译为一个 Pattern 对象

  2. 通过 Pattern 对象提供的一系列方法对文本进行匹配查找,获得匹配结果,一个 Match 对象。

  3. 最后使用 Match 对象提供的属性和方法获得信息,根据需要进行其他的操作

compile函数:

compile 函数用于编译正则表达式,生成一个 Pattern 对象。

Pattern 对象的一些常用方法主要有:

  • match 方法:从起始位置开始查找,一次匹配
  • search 方法:从任何位置开始查找,一次匹配
  • findall 方法:全部匹配,返回列表
  • finditer 方法:全部匹配,返回迭代器
  • split 方法:分割字符串,返回列表
  • sub 方法:替换

match 方法用于查找字符串的头部(也可以指定起始位置),它是一次匹配,只要找到了一个匹配的结果就返回,而不是查找所有匹配的结果。它的一般使用形式如下:match(string[, pos[, endpos]])

其中,string 是待匹配的字符串,pos 和 endpos 是可选参数,指定字符串的起始和终点位置,默认值分别是 0 和 len (字符串长度)。因此,当你不指定 pos 和 endpos 时,match 方法默认匹配字符串的头部。

当匹配成功时,返回一个 Match 对象,如果没有匹配上,则返回 None。

下面是一个简单的案例:

import re
pattern = re.compile(r'\d+')  # 用于匹配至少一个数字
#上面这个正则表达式其实就是匹配数字开头的一个或者多个,所以我们在匹配时一定要以数字开头
m = pattern.match('one12twothree34four')# 查找头部,没有匹配
print(m)#因为不匹配所以返回none
m = pattern.match('one12twothree34four', 2, 10) # 从'e'的位置开始匹配,没有匹配
print(m)
m = pattern.match('one12twothree34four', 3, 10) # 从'1'的位置开始匹配,正好匹配
print(m)                                       # 返回一个 Match 对象
print(m.group(0))  # 可省略 0(这个就是把匹配的结果打印出来)

print(m.start(0))  # 可省略 0(这个是获取字符串的起始位置)

print(m.end(0))     # 可省略 0

print(m.span(0))   # 可省略 0(获取字符串起始和结束位置)

######################案例2
import re
pattern = re.compile(r'([a-z]+) ([a-z]+)', re.I)  # re.I 表示忽略大小写
m = pattern.match('Hello World Wide Web')
#上面这个是匹配字母
print(m)     # 匹配成功,返回一个 Match 对象

print(m.group(0)) # 返回匹配成功的整个子串


print(m.span(0))  # 返回匹配成功的整个子串的索引


print(m.group(1))  # 返回第一个分组匹配成功的子串


print(m.span(1))   # 返回第一个分组匹配成功的子串的索引


print(m.group(2))  # 返回第二个分组匹配成功的子串


print(m.span(2))  # 返回第二个分组匹配成功的子串


print(m.groups())  # 等价于 (m.group(1), m.group(2), ...)

search 方法

search 方法用于查找字符串的任何位置,它也是一次匹配,只要找到了一个匹配的结果就返回,而不是查找所有匹配的结果,它的一般使用形式如下:search(string[, pos[, endpos]])

其中,string 是待匹配的字符串,pos 和 endpos 是可:选参数,指定字符串的起始和终点位置,默认值分别是 0 和 len (字符串长度)。

当匹配成功时,返回一个 Match 对象,如果没有匹配上,则返回 None。

import re
pattern = re.compile('\d+')
m = pattern.search('one12twothree34four')  # 这里如果使用 match 方法则不匹配
print(m)
print(m.group())
m = pattern.search('one12twothree34four', 10, 30)  # 指定字符串区间
print(m)
print(m.group())
print(m.span())
######输出结果
<_sre.SRE_Match object; span=(3, 5), match='12'>
12
<_sre.SRE_Match object; span=(13, 15), match='34'>
34
(13, 15)

findall 方法

上面的 match 和 search 方法都是一次匹配,只要找到了一个匹配的结果就返回。然而,在大多数时候,我们需要搜索整个字符串,获得所有匹配的结果。

findall 方法的使用形式如下:findall(string[, pos[, endpos]])

其中,string 是待匹配的字符串,pos 和 endpos 是可选参数,指定字符串的起始和终点位置,默认值分别是 0 和 len (字符串长度)。

findall 以列表形式返回全部能匹配的子串,如果没有匹配,则返回一个空列表。

finditer 方法

finditer 方法的行为跟 findall 的行为类似,也是搜索整个字符串,获得所有匹配的结果。但它返回一个顺序访问每一个匹配结果(Match 对象)的迭代器。

实际案例:

import re
pattern = re.compile(r'\d+')

result_iter1 = pattern.finditer('hello 123456 789')
result_iter2 = pattern.finditer('one1two2three3four4', 0, 10)

print(type(result_iter1))
print(type(result_iter2))

print('result1...')
for m1 in result_iter1:   # m1 是 Match 对象
    print(str(m1.group())+'---'+str(m1.span()))

print('result2...')
for m2 in result_iter2:
    print (str(m2.group())+'---'+str(m2.span()))
######输出结果


result1...
123456---(6, 12)
789---(13, 16)
result2...
1---(3, 4)
2---(7, 8)

split 方法

split 方法按照能够匹配的子串将字符串分割后返回列表,它的使用形式如下:split(string[, maxsplit])

其中,maxsplit 用于指定最大分割次数,不指定将全部分割。

案例演示:

import re
p = re.compile(r'[\s\,\;]+')
print(p.findall('a,b;; c   d'))
print(p.split('a,b;; c   d'))
###########输出结果
[',', ';; ', '   ']
['a', 'b', 'c', 'd']

sub 方法

sub 方法用于替换。它的使用形式如下:sub(repl, string[, count])

其中,repl 可以是字符串也可以是一个函数:

  • 如果 repl 是字符串,则会使用 repl 去替换字符串每一个匹配的子串,并返回替换后的字符串,另外,repl 还可以使用 id 的形式来引用分组,但不能使用编号 0;
  • 如果 repl 是函数,这个方法应当只接受一个参数(Match 对象),并返回一个字符串用于替换(返回的字符串中不能再引用分组)。
  • count 用于指定最多替换次数,不指定时全部替换。
import re
p = re.compile(r'(\w+) (\w+)') # \w = [A-Za-z0-9]
s = 'hello 123, hello 456'

print(p.sub(r'hello world', s) ) # 使用 'hello world' 替换 'hello 123' 和 'hello 456'
print (p.sub(r'\2 \1', s) )       # 引用分组

def func(m):
    return 'hi' + ' ' + m.group(2)

print (p.sub(func, s))
print (p.sub(func, s, 1)  )       # 最多替换一次
###########输出结果
hello world, hello world
123 hello, 456 hello
hi 123, hi 456
hi 123, hello 456

匹配中文

在某些情况下,我们想匹配文本中的汉字,有一点需要注意的是,中文的 unicode 编码范围 主要在 [u4e00-u9fa5],这里说主要是因为这个范围并不完整,比如没有包括全角(中文)标点,不过,在大部分情况下,应该是够用的。

假设现在想把字符串 title = u'你好,hello,世界' 中的中文提取出来,可以这么做:

注意:我用Python3.6的好像没用,用2.7的实验成功


import re
title = u'你好,hello,世界'
pattern = re.compile(ur"[\u4e00-\u9fa5]+")
result = pattern.findall(title)

print(result)
###########输出结果
[u'\u4f60\u597d', u'\u4e16\u754c']

注意:贪婪模式与非贪婪模式

  1. 贪婪模式:在整个表达式匹配成功的前提下,尽可能多的匹配 ( * );
  2. 非贪婪模式:在整个表达式匹配成功的前提下,尽可能少的匹配 ( ? );
  3. Python里数量词默认是贪婪的。

示例一 : 源字符串:abbbc

  • 使用贪婪的数量词的正则表达式 ab* ,匹配结果: abbb。

    * 决定了尽可能多匹配 b,所以a后面所有的 b 都出现了。

  • 使用非贪婪的数量词的正则表达式ab*?,匹配结果: a。

    即使前面有 *,但是 ? 决定了尽可能少匹配 b,所以没有 b。

示例二 : 源字符串:aa<div>test1</div>bb<div>test2</div>cc

  • 使用贪婪的数量词的正则表达式:<div>.*</div>

  • 匹配结果:<div>test1</div>bb<div>test2</div>

这里采用的是贪婪模式。在匹配到第一个“</div>”时已经可以使整个表达式匹配成功,但是由于采用的是贪婪模式,所以仍然要向右尝试匹配,查看是否还有更长的可以成功匹配的子串。匹配到第二个“</div>”后,向右再没有可以成功匹配的子串,匹配结束,匹配结果为“<div>test1</div>bb<div>test2</div>


  • 使用非贪婪的数量词的正则表达式:<div>.*?</div>

  • 匹配结果:<div>test1</div>

正则表达式二采用的是非贪婪模式,在匹配到第一个“</div>”时使整个表达式匹配成功,由于采用的是非贪婪模式,所以结束匹配,不再向右尝试,匹配结果为“<div>test1</div>”。

实际案例:使用正则表达式爬内涵段子网站:

首先就是先通过requests请求来获取网页数据,然后我们需要编一下码,最后用正则表达式匹配一下就行了:

源码(做的非常简陋):


import requests
import re
def loadPage(page):
    url = "https://www.neihan-8.com/article/index_"+str(page)+ ".html"
    #User-Agent头
    user_agent = "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.96 Safari/537.36"
    headers = {'User-Agent': user_agent}
    req =requests.get(url=url,headers=headers)
    req.encoding=('utf-8')
    return req.text
#     print(html)   

a=loadPage(2)
pattern = re.compile(r'<div.?class="desc">(.*?)</div>', re.S)
item_list = pattern.findall(a)
for item in item_list:
    item = item.replace("\u3000\u3000", "")
    print(item)
  • 这里需要注意一个是re.S是正则表达式中匹配的一个参数。
  • 如果 没有re.S 则是 只匹配一行 有没有符合规则的字符串,如果没有则下一行重新匹配。
  • 如果 加上re.S 则是将 所有的字符串 将一个整体进行匹配,findall 将所有匹配到的结果封装到一个list中。

使用XPath

lxml库

lxml 是 一个HTML/XML的解析器,主要的功能是如何解析和提取 HTML/XML 数据。

lxml和正则一样,也是用 C 实现的,是一款高性能的 Python HTML/XML 解析器,我们可以利用之前学习的XPath语法,来快速的定位特定元素以及节点信息。

lxml python 官方文档:http://lxml.de/index.html

需要安装C语言库,可使用 pip 安装:pip install lxml (或通过wheel方式安装)

这里先说一个实例:

from lxml import etree 

text = '''
''' #利用etree.HTML,将字符串解析为HTML文档 html = etree.HTML(text) result=html.xpath('//li')#这里可以直接进行查找 # 可以利用etree.parse() 方法来读取文件。 print(etree.tostring(result[0]))# 按字符串序列化HTML文档

还有一些其他的用法:

获取<li> 标签的所有 class属性:result = html.xpath('//li/@class')

继续获取<li>标签下hre 为 link1.html 的 <a> 标签:result = html.xpath('//li/a[@href="link1.html"]')

获取<li> 标签下的所有 <span> 标签:result = html.xpath('//li//span')

获取 <li> 标签下的<a>标签里的所有 class:result = html.xpath('//li/a//@class')

获取最后一个 <li> 的 <a> 的 href:result = html.xpath('//li[last()]/a/@href') # 谓语 [last()] 可以找到最后一个元素

获取倒数第二个元素的内容:result = html.xpath('//li[last()-1]/a')

获取 class 值为 bold 的标签名:result = html.xpath('//*[@class="bold"]')

那么,我们现在直接开始实战:

我们这次还是继续爬内涵段子(例子非常简单):

import requests
from lxml import etree 
url='https://www.neihan-8.com/article/index_2.html'
user_agent = "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.96 Safari/537.36"
headers = {'User-Agent': user_agent}
response = requests.get(url=url, headers = headers)
response.encoding = 'utf8'
result=etree.HTML(response.text)
s = result.xpath('//div[@class="desc"]')
for i in s:
    print(i.text)

实际输出:

有内涵的笑话他本身不仅仅是一个笑话,且具有深刻的内涵意义,需要探索挖掘才能看得懂。每日更新内涵荤段子笑话……和女神聊天,聊了一会女神居然问我,你有那种网站么?大家懂的,我就说有啊有啊,然后发了过去。结果她说:有了就赶紧去看吧,和你聊天挺烦的。。...   大街上女孩吃冰淇淋时,吃一口后就把冰淇淋送到了男孩口里,就这样二人轮着吃。  小莉见了羡慕地对男人说:“你看人家多亲热!”   男人见了一笑说:“亲热啥呀!如果身上有   一对年轻夫妇走进首饰商店,妻子问售货员:“右边的那个戒指要多少钱?”  “1万元。”  丈夫惊愕地吹了一个口哨,问道:“在它旁边的那个呢?”  售货员答道:“两个口哨的   最近这几天感冒了。早上去坐公交,忍不住打了个喷嚏&hellip;&hellip;  只听一个女的娇滴滴地说:“老公,听说最近甲流很流行,被传染了怎么办?”  只听旁边的壮汉冷冷地说   女儿问妈妈:“妈妈,人为什么会牙痛呢?”  妈妈马上意识到,这是一个绝好的教育时机。“一般来说是因为吃糖太多,不过,其他的坏毛病,比如撒谎、横穿马路、吃饭不洗手&hellip;   小孩甲:你知道什么糖最贵?  小孩乙:巧克力。  小孩甲:不对,喜糖最贵。我妈妈花了二十块钱才买了两袋,总共十六颗。   这天,主持人在广播里回答听众的提问,一名妇女打进电话,紧张地大叫:“我家地下室来了一只黄鼠狼,怎么办?”  主持人从来没遇上这样的问题,他想了想,建议说:“在你们家从地下室   幼儿园的老师向班上的小朋友提问:“小珍,你能说出你家在哪儿吗?”  小珍一板一眼地回答:“我家在双楠大厦。”  “双楠大厦几楼几号?”  小珍这下不知怎么回答,愣在   妻子临睡前絮絮叨叨的谈话令丈夫十分不快。一天夜里,妻子又絮叨了一阵子后,问丈夫:“家里的窗门都关好了吗?”  丈夫回答:“除了你的话匣子外,该关的都关了。”   妻子患重病,丈夫寸步不离地守候在一边。  妻子问丈夫说:“你老实告诉我,我死后你打算怎么办?”  丈夫说:“不要问我这个问题,我会疯掉的。”  “你会不会再婚?”     结婚三年,老婆问老公:“我现在看你,好像没有那种心跳的感觉了,你对我还有吗?”  老公说:“一直都跳。而且不是简单的心跳,是心惊肉跳。” 妻子问老公:“你爱我吗?”  老   结婚三年,老婆问老公:“我现在看你,好像没有那种心跳的感觉了,你对我还有吗?”  老公说:“一直都跳。而且不是简单的心跳,是心惊肉跳。”   女儿莲达4岁大,可以24小时喋喋不休,有时连我也会觉得不耐烦。  一天,她跑过来给我讲故事,直到她说到一半,我才发觉自己并没有留心听,于是请她从头说起。  她恼怒地说:“妈   上初中的时候,班主任是个老头,和我们关系特别好,他平时就好抽烟,学校是禁烟的,就总看他在学校门口抽。  有次上语文课,正在学一首诗,有句话是这样的“你托起手中的宝塔山”, 和王嫂开玩笑,说道:“嫂子,你是这么看上我宋哥的?又矮又难看的,”王嫂:“他呀,年轻时候是开手扶拖拉机的,”我说:“开手扶拖拉机怎么了,那时候很赚钱吗?”王嫂笑了笑穿上衣服做饭去了,留   有段时间帮朋友照看服装店。  这天进来俩美女,身材暴好。  相中一件上衣,要价125。问要两件能否便宜点。  本人答,最多差5元。  两女一阵嘀咕,完了弱弱的问我:两件3   黄大夫是专攻不孕症的妇产科医生,他行医多年,治愈众多久婚不孕的妇人。  一日,某名感恩的病人送来一块匾额以表谢意,上书:无中生有。   一位可爱的少女,在午餐时间去看医生,在诊所到一名穿白衣英俊潇的年轻人。  少女说:“我的肩膀疼了一个礼拜,你能帮我看一下吗?”  这名白衣年轻人说:“你躺在床上,我来替   局长与科长共乘电梯,局长放一屁后,对科长说:“你放屁了!”  科长说:“不是我放的&hellip;”  不久科长被免职,局长在会上说:“屁大的事你都担待不起,要你何用?”   阿呆对一个朋友说:“上次面试我真是失败极了。”  朋友问:“怎么了?”  阿呆说:“考官问我有特长没有,我说,我今天没有带特产,希望有机会请考官去我家乡品尝。”   北京某高干子弟和山西煤老板儿子吃饭,高干子弟大声道:“给老子一百万,在北京没有我办不到的事”。  煤老板儿子听后小声说:“哥,我给你一个亿,能不能把天安门城楼上那张照

点赞

发表评论

昵称和uid可以选填一个,填邮箱必填(留言回复后将会发邮件给你)
tips:输入uid可以快速获得你的昵称和头像

Title - Artist
0:00