首页 > 技术文章 > python爬虫笔记

lizhihoublog 2020-03-23 09:05 原文

##爬虫
-通用网络爬虫
其的主要目的是将互联网上的资源下载到本地形成一个镜像备份。类似百度等搜索引擎

-聚焦爬虫
其面向特定需求的爬虫程序,与通用爬虫的区别在于对数据进行筛选尽量爬取相关数据

-爬虫优化
1.尽量减少请求次数
2.web页面不好爬取时爬apph5页面(手机)

##HTTPHTTPS
--超文本传输协议
HTTP 端口80
HTTPS时加入了ssl安全传输协议 端口443


##get请求方式获取
---get方式一般用于向服务器获取数据
--parse用于url编码
实际使用是将url地址与想要查询的数据信息拼接形成完整地url
例如
# https://tieba.baidu.com/f?ie=utf-8&kw=火影忍者&fr=search
# https://tieba.baidu.com/f?kw=火影忍者&ie=utf-8&pn=50
base_url = 'http://wwww.baidu.com/f?'
# 搜索信息关键字
wd = input('搜索信息关键字')
pn = input('pn')
q_data = {
'wd' = wd,
'pn' = pn,
}
# q_data进行编码使用parse模块
q_data = parse.urlencode(q_data)
# 拼接url
full_url = base_url + q_data
# 构造请求对象
req = request.Request(url=full_url,headers=headers)
# 获取响应对象
response = request.urlopen(req).read().decode('utf-8')


##post请求方式
--post方式需要向服务器发送一些数据
需要注意的是post传数据时不仅要使用parse转码还需要将其转化为字节形式
例如:
base_url = 'http://wwww.baidu.com/'
headers = {
'User-Agent': "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:72.0) Gecko/20100101 Firefox/72.0",
}
# post携带数据
kw = input(">>")
data = {
'kw': kw
}
parse_data = parse.urlencode(data)

req = request.Request(url=base_url,data=bytes(data,encoding='utf-8'),headers=headers)
response = request.urlopen(req).read().decode('utf-8')


##cookies
--获取cookies
cookies_object = cookiejar.CookieJar()
cookies_handler = request.HTTPCookieProcessor(cookies_object)
http_handler = request.HTTPHandler()
https_handler = request.HTTPSHandler()
opener = request.build_opener(http_handler,https_handler,cookies_handler)

##ssl
---免认证
# 忽略未经核实的ssl证书认证
ssl._create_default_https_context = ssl._create_unverified_context()

##登录网页爬取开心网
---登录url
https://security.kaixin001.com/login/login_post.php
邮箱:loginemail
密码:password

##有道翻译
'''
分析:
爬取步骤
1 获取url
2 data数据填写 headers数据填写
3 构造request对象生成响应
data中的数据{
i "job"
from "AUTO"
to "AUTO"
smartresult "dict"
client "fanyideskweb"
salt "15842516730329"
sign "60f53618f3fa667e6d3192148c8c1a03"
ts "1584251673032"
bv "e2a78ed30c66e16a857c5b6486a1d326"
doctype "json"
version "2.1"
keyfrom "fanyi.web"
action "FY_BY_REALTlME"
}
headers中的数据{
Host: fanyi.youdao.com
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:74.0) Gecko/20100101 Firefox/74.0
Accept: application/json, text/javascript, */*; q=0.01
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate # 此行使用时需要注释掉 不要接受压缩信息文件
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
X-Requested-With: XMLHttpRequest
Content-Length: 236
Origin: http://fanyi.youdao.com
Connection: keep-alive
Referer: http://fanyi.youdao.com/
# 我写代码时cookie有问题换了别人的cookie值可以正常爬取而我的会生成{‘errorcode’:50}错误
Cookie: OUTFOX_SEARCH_USER_ID=-140868279@123.121.59.79; OUTFOX_SEARCH_USER_ID_NCOO=1766192849.313334; _ntes_nnid=700fcdc75a16b68417175b615d961ea3,1537510225088; YOUDAO_MOBILE_ACCESS_TYPE=1; JSESSIONID=aaa_XsoMzWSA17Bi3OCdx; ___rl__test__cookies=1584251673023
}
难点
因为是post方式提交的数据所以在浏览器调试模式中查找请求方式为post的进行查看
post提交数据中i 为查询的数据 saltsignts 会发生变化
获取saltsign 的加密方式 在 fanyi.min.js 文件中使用代码格式化工具https://tool.oschina.net/codeformat/js 查找saltsign关键字
salt: i i = r + parseInt(10 * Math.random(), 10);
sign: n.md5("fanyideskweb" + e + i + "Nw(nmmbP%A-r6U3EUn]Aj")
ts:r r = "" + (new Date).getTime(),
'''


##豆瓣电影分类排行
分析
爬取步骤
1.获取url信息
2.数据爬取
3.数据保存

页面分析一次显示20条信息
json数据中的请求url显示为:
https://movie.douban.com/j/chart/top_list?type=11&interval_id=100%3A90&action=&start=0&limit=20
https://movie.douban.com/j/chart/top_list?type=11&interval_id=100%3A90&action=&start=20&limit=20
https://movie.douban.com/j/chart/top_list?type=11&interval_id=100%3A90&action=&start=40&limit=20
start 为起始点
limit 为显示信息
type 为剧情类型

难点:
需要爬取所有类型的type_id
response = requests.get(url=url, headers=headers)
douban_html = etree.HTML(response.text)
datas = douban_html.xpath("//div[@class='article']/div[2]/div[@class='types']/span")

for href in datas:
info = href.xpath(".//@href")[0].split('&')[1]
name = href.xpath(".//a")[0].text
movies_type[name] = info

##代理ip
# ProxyHandler代理ip
# 免费短期代理网站举例:
# 西刺免费代理 IP:http://www.xicidaili.com/
# 快代理免费代理:https://www.kuaidaili.com/free/inha/
# 全网代理 IP:http://www.goubanjia.com/
'''
urllib中的request模块
1.定义代理对象
2.创建opener对象使用代理
3.构造请求对象
4.发送请求

'''
from urllib import request
import random
# 单个代理ip
# proxy_ = request.ProxyHandler({
# # 代理ip
# "http":'122.224.65.198:3128',
# })

# 多个ip代理使用 也可以从文件中读取
proxy_list = [
{"http":'122.224.65.198:3128'},
{"http":'111.160.169.54:42626'},
{"http":'116.196.87.86:20183'},
{"http":'123.58.17.134:3128'},
]
proxy_ = random.choice(proxy_list)

# 生成代理对象
proxy_handler = request.ProxyHandler(proxy_)

opener = request.build_opener(proxy_handler)

# 构造请求
req = request.Request(url='http://wwww.baidu.com/')
# 发送请求
response = opener.open(req)


##西刺代理ip爬取


##request模块使用
####get方式
# 导入模块
import requests
# get 无参
response = requests.get('url')
# 还可以用request 方法request("请求方法"'url')
response = requests.request("get",'url')
# get 带参数 params参数接收字典或字符串
response = requests.get('url',params=dict1{},headers=headers)
####post方式
# 导入模块
import requests
# data = {
# # 提交数据
# }
# response = requests.request("post",url='',data=data,headers=headers)

#### ip代理
# proxies 参数 公开代理ip
# proxies = [
# {"http": '122.224.65.198:3128'},
# {"http": '111.160.169.54:42626'},
# {"http": '116.196.87.86:20183'},
# {"http": '123.58.17.134:3128'},
# ]
# response = requests.request('get',url='',params=dict1,headers=headers,proxies=proxies)

# proxies 参数使用 私密代理
# proxy = {"http":"用户名:密码@ipd地址:端口"}
# response = requests.request('get',url='',params=dict1,headers=headers,proxies=proxy)


#### 获取数据后
response.text # 返回Unicode格式数据
response.content # 返回字节流数据用于保存二进制数据
response.json # 返回json文件
response.url # 返回完整url
response.encoding # 返回响应编码格式
response.status_code # 返回响应码

##cookiessession
# cookies_obj = response.cookies # 获取cookies对象
# cookies_dict = response.utils.dict_from_cookiejar(cookies_obj) # cookies转换为字典
'''
session 对象获取cookies保存以便访问登录之后的页面
'''
# 生成session对象
session_obj = requests.session()
# 发送post请求登录页面
form_data = {
'email':'',
'password':'',
}
session_obj.post(url='',data=form_data)
# 此时cookies存储在session_obj中可以利用session_obj直接访问页面
response = session_obj.get(url='')
##全国邮编爬取
设置解码格式防止出现乱码
response.encoding = 'gb2312'
'''
https://www.ip138.com/post/
https://www.ip138.com/10/ 北京邮编url
//table/tbody/tr[2] 北京邮编xpath
'''

##scrapy安装
-选择相应按本的twisted
进入https://www.lfd.uci.edu/~gohlke/pythonlibs/,下载对应的whl文件。cp后面是python版本,amd64代表64win32代表32
命令:
pip install C:\Users\Administrator\Desktop\Twisted-19.10.0-cp38-cp38-win32.whl
安装scrapy
pip install -i https://pypi.doubanio.com/simple/ --trusted-host pypi.doubanio.com scrapy

-建立项目
命令行输入:
scrapy startproject 项目名

-scrapy框架结构与工作原理
引擎,调度器,下载器,爬虫,数据管道,中间件
spider发现URL生成request对提交给引擎随后进入调度器排队,之后进入下载器,下载器根据request对象的URL发送HTTP请求返回一个response对象
发送给spider的解析函数处理,将提取的数据封装成item提交给引擎,然后交给数据管道处理,其中若页面中有URL则重复以上步骤

##框架解析
scrapy.cfg :项目的配置文件
mySpider/ :项目的 Python 模块,将会从这里引用代码
mySpider/items.py :项目的目标文件
mySpider/pipelines.py :项目的管道文件
mySpider/settings.py :项目的设置文件
mySpider/spiders/ :存储爬虫代码目录

##items
Item 定义结构化数据字段,用来保存爬取到的数据。明确存储信息。
可以通过创建一个 scrapy.Item 类, 并且定义类型为 scrapy.Field 的类属性来定义一个 Item(可以理解成类似于 ORM 的映射关系)。
例子:
class MeiJuItem(scrapy.Item):
'''
爬取美剧
定义MeiJuItem类继承scrapy.Item
定义存储数据
'''
name = scrapy.Field()
href = scrapy.Field()
state = scrapy.Field()
tv = scrapy.Field()
time = scrapy.Field()

##spider
-爬虫编写
步骤
1.导入scrapy库和相应item
2.继承scrapyspider
3.name 用于唯一标识spider start_urls 用于存放需要爬取的URL
4.重写页面解析函数parse
-parse方法
1.提取页面数据
2.提出新的页面请求
例子:
import scrapy
from example.items import MeiJuItem
class MeiJuSpider(scrapy.Spider):
name = 'meiju'
start_urls = ["https://www.meijutt.tv/new100.html"]

def parse(self, response):
page_data = response.xpath(".//ul[@class='top-list fn-clear']/li")

for li_data in page_data:
item = MeiJuItem()
item['name'] = li_data.xpath("./h5/a/@title").extract()[0]
item['href'] = li_data.xpath("./h5/a/@href").extract()[0]
item['state'] = li_data.xpath("./span[@class='state1 new100state1']/string()").extract()[0]
item['tv'] = li_data.xpath("./span[@class='mjtv']/text()").extract()[0]
item['time'] = li_data.xpath("./div[@class='lasted-time new100time fn-right']/text()").extract()[0]
yield item

##item pipeline
以下是Item Pipeline的几种典型应用:
清洗数据。
验证数据的有效性。
过滤掉重复的数据。
将数据存入数据库。
● open_spider(self, spider) Spider打开时(处理数据前)回调该方法,
通常该方法用于 在开始处理数据之前完成某些初始化工作,如连接数据库。
● close_spider(self, spider) Spider关闭时(处理数据后)回调该方法,
通常该方法用于 在处理完所有数据之后完成某些清理工作,如关闭数据库。
● from_crawler(cls, crawler) 创建Item Pipeline对象时回调该类方法。
通常,在该方法中通过crawler.settings读取配置,根据配置创建Item Pipeline对象。
编写 item pipeline 很简单,item pipiline 组件是一个独立的 Python 类,其中 process_item() 方法必须实现
例子
class MeiJuPipeline(object):

def __init__(self):
# 可选实现,做参数初始化等
self.file = open('meiju.json','wb')
pass

def process_item(self,item,spider):
'''
# item (Item 对象) – 被爬取的 item
# spider (Spider 对象) – 爬取该 item spider
# 这个方法必须实现,每个 item pipeline 组件都需要调用该方法,
# 这个方法必须返回一个 Item 对象,被丢弃的 item 将不会被之后的 pipeline 组件 所处理。
'''
json.doump(dict(item),open('meiju.json','a',encoding='utf-8'),ensure_ascii=False)
return item
pass

def open_spider(self, spider):
# spider (Spider 对象) – 被开启的 spider
# 可选实现,当 spider 被开启时,这个方法被调用。
pass

def close_spider(self,spider):
# spider (Spider 对象) – 被关闭的 spider
# 可选实现,当 spider 被关闭时,这个方法被调用
self.file.close()

##log
setting中配置
LOG_FILE = "meiju.log"
LOG_LEVEL = "INFO"

##middlewares
中间键
DOWNLOADER_MIDDLEWARES = { 'scrapy_crawlera.CrawleraMiddleware': 600 }
代理
Scrapy 代理 IPUesr-Agent 的切换都是通过 DOWNLOADER_MIDDLEWARES 进行控制
CRAWLERA_ENABLED = True
CRAWLERA_USER = '注册/购买的
UserKey' CRAWLERA_PASS = '注册/购买的 Password
或者
修改 settings.py 配置 USER_AGENTS PROXIES
USER_AGENTS = [ "Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Win64; x64; Trident/5.0; .NET CLR 3.5.30729; .NET CLR 3.0.30729; .NET CLR 2.0.50727; Media Center PC 6.0)",
"Mozilla/5.0 (compatible; MSIE 8.0; Windows NT 6.0; Trident/4.0; WOW64; Trident/4.0;
]
PROXIES = [
{'ip_port': '111.8.60.9:8123', 'user_passwd': 'user1:pass1'},
{'ip_port': '101.71.27.120:80', 'user_passwd': 'user2:pass2'},
{'ip_port': '122.96.59.104:80', 'user_passwd': 'user3:pass3'},
{'ip_port': '122.224.249.122:8088', 'user_passwd': 'user4:pass4'}
]
下载延迟settings文件中配置
DOWNLOAD_DELAY = 3
例子:
import random
import base64
from settings import USER_AGENTS
from settings import PROXIES
# 随机的 User-Agent
class RandomUserAgent(object):
def process_request(self, request, spider):
useragent = random.choice(USER_AGENTS)
request.headers.setdefault("User-Agent", useragent)

class RandomProxy(object):
def process_request(self, request, spider):
proxy = random.choice(PROXIES)
if proxy['user_passwd'] is None:
# 没有代理账户验证的代理使用方式
request.meta['proxy'] = "http://" + proxy['ip_port']
else:
# 对账户密码进行 base64 编码转换
base64_userpasswd = base64.b64encode(proxy['user_passwd'])
# 对应到代理服务器的信令格式里
request.headers['Proxy-Authorization'] = 'Basic ' + base64_userpasswd
request.meta['proxy'] = "http://" + proxy['ip_port']

##settings
scrapy 当中,可以在 scrapy.Request 中加入 dont_filter=False(默认去重)

###xpath
XPath 中,有七种类型的节点:元素、属性、文本、命名空间、处理指令、注释以及文档(根)节点。
XML 文档是被作为节点树来对待的。树的根被称为文档节点或者根节点。
--节点关系
--父节点
每个元素以及属性都有一个父。
在例子中book 元素是 titleauthoryear 以及 price 元素的父
--子节点
元素节点可有零个、一个或多个子。
在例子中titleauthoryear 以及 price 元素都是 book 元素的子
--同级节点
拥有相同的父的节点
在下面的例子中,titleauthoryear 以及 price 元素都是同级节点
-例子

<book>
<title>Harry Potter</title>
<author>J K. Rowling</author>
<year>2005</year>
<price>29.99</price>
</book>
--xpath语法
XPath 使用路径表达式在 XML 文档中选取节点。
nodename 选取此节点的所有子节点。
/ 从根节点选取。
// 从匹配选择的当前节点选择文档中的节点,而不考虑它们的位置。
. 选取当前节点。
.. 选取当前节点的父节点。
@ 选取属性。
--例子
bookstore 选取 bookstore 元素的所有子节点。
/bookstore 选取根元素 bookstore。注释:假如路径起始于正斜杠( / ),则此路径始终代表到某元素的绝对路径!
bookstore/book 选取属于 bookstore 的子元素的所有 book 元素。
//book 选取所有 book 子元素,而不管它们在文档中的位置。
bookstore//book 选择属于 bookstore 元素的后代的所有 book 元素,而不管它们位于 bookstore 之下的什么位置。
//@lang 选取名为 lang 的所有属性。

--复杂例子
/bookstore/book[1] 选取属于 bookstore 子元素的第一个 book 元素。
/bookstore/book[last()] 选取属于 bookstore 子元素的最后一个 book 元素。
/bookstore/book[last()-1] 选取属于 bookstore 子元素的倒数第二个 book 元素。
/bookstore/book[position()<3] 选取最前面的两个属于 bookstore 元素的子元素的 book 元素。
//title[@lang] 选取所有拥有名为 lang 的属性的 title 元素。
//title[@lang='eng'] 选取所有 title 元素,且这些元素拥有值为 eng lang 属性。
/bookstore/book[price>35.00] 选取 bookstore 元素的所有 book 元素,且其中的 price 元素的值须大于 35.00
/bookstore/book[price>35.00]//title 选取 bookstore 元素中的 book 元素的所有 title 元素,且其中的 price 元素的值须大于 35.00

--xpath使用
导入lxml中的etree模块
使用etreeHTML方法将页面信息转化为xml文档
此时可以使用xml对象进行xpath数据提取


###bs4
---bs4自带转码功能不需要转码操作,其将HTML转换为树形结构
---tag 对应 标签 使用 对象名.标签名 返回第一个满足条件的标签
name name属性获取标签名称
例如:
name = soup.img.name
attrs attrs属性获取标签的属性生成字典
例如:
img = soup.img.attrs
src = img.get("src")
string string属性获取标签中的文本
例如:
text = soup.title.string
contents contents属性获取直接子节点返回一个列表
children children属性返回一个生成器

---文档树搜索
find__all(name,attrs,recursive,text,**kwargs)
name --标签名称
----参数
字符串 -直接查询标签
正则表达式 -查询满足正则的所有标签
列表 -查询和列表中匹配的标签
attrs --标签属性
----参数
属性名= -查询满足条件的标签
text --标签内容
----参数和name一样接收字符串、正则表达式、列表

css选择器
类名前加 . id名前加 # 标签名不加修饰
select() 返回结果为列表
标签名查找 soup.select(标签名) 例如: soup.select('title')
类名查找 soup.select('.类名') 例如: soup.select('.input')
id名查找 soup.select('#id') 例如: soup.select('#head_wrapper')
组合查找 soup.select('div .input')
soup.select('div #head_wrapper')
属性查找 soup.select('div img[class="weixin"]')
内容获取 使用get_text()方法
---scrapy
-标签提取
response.css('title')
获取到一个selector列表例如:
[<Selector xpath='descendant-or-self::title' data='<title>百度一下,你就知道</title>'>]


-extract方法获取到标签列表
['<title>百度一下,你就知道</title>']

-extract_first方法获取到标签
'<title>百度一下,你就知道</title>'

-::text 获取到标签中的数据
response.css('title::text').extract_first()
---- response.css('标签名::text').extract_first()

-class选择器
<div class="quote post">中的class
info.css('.text::text').extract_first()
对象.css('标签.class名称::text').extract_first()

-ID选择器
<div id="quote">中的id
info.css('#quote::text').extract_first()
对象.css('标签#id名称::text').extract_first()


---
1.导入模块
from bs4 import BeautifulSoup
2.生成对象
soup=BeautifulSoup(html文档,'lxml')
3.格式化输出对象内容
content= soup.prettify()

##re
match 方法:从起始位置开始查找,一次匹配
search 方法:从任何位置开始查找,一次匹配
findall 方法:全部匹配,返回列表
finditer 方法:全部匹配,返回迭代器
split 方法:分割字符串,返回列表
sub 方法:替换
^ 匹配字符串的开头
$ 匹配字符串的末尾。
. 匹配任意字符,除了换行符,当re.DOTALL标记被指定时,则可以匹配包括换行符的任意字符。
[...] 用来表示一组字符,单独列出:[amk] 匹配 'a''m''k'
[^...] 不在[]中的字符:[^abc] 匹配除了a,b,c之外的字符
\w 匹配数字字母下划线
\W 匹配非数字字母下划线
\s 匹配任意空白字符,等价于 [\t\n\r\f]
\S 匹配任意非空字符
\d 匹配任意数字,等价于 [0-9]
\D 匹配任意非数字
* 匹配0个或多个的表达式。
+ 匹配1个或多个的表达式。
? 匹配0个或1个由前面的正则表达式定义的片段,非贪婪方式`

##json模块
用于对json类型与python类型相互转换
loads json.loads("json字符串")
dumps json.dumps("python字符串")
dump json.dump()

推荐阅读