学习总结
从进入科研室以来,主要学习了以下内容:
一、初步接触爬虫,第一个任务就是根据视频所讲的内容和所举的例子,利用正则表达式爬取单页百度贴吧图片。
爬取步骤大致分为两步,分别定义两个函数,
第一个函数的目的是将需要爬取进行遍历,
def getHtml(url):
page = urllib2.urlopen(url)
html = page.read()
return html #函数中有return返回值才是完整的函数。默认返回None
Urllib2模块urlopen()函数:
urlopen(url, data=None, proxies=None)
创建一个表示远程url的类文件对象,然后像本地文件一样操作这个类文件对象来获取远程数据。
参数url表示远程数据的路径,一般是网址;
参数data表示以post方式提交到url的数据(玩过web的人应该知道提交数据的两种方式:post与get);
参数proxies用于设置代理。
第二个函数的目的是写出正则爬取所需内容。
def getImg(html):
reg = r'src="(.+?\.jpg)" pic_ext'
imgre = re.compile(reg) #将正则表达式转为模式对象,实现高效匹配
imglist = re.findall(imgre, html) #以列表的形式返回能匹配的子串
x = 0
for imgurl in imglist:
urllib2.urlretrieve(imgurl, '%s.jpg' % x) #直接将远程数据下载到本地
x += 1
难点:对于正则表达式的正确书写以及对于自定义函数的调用。
解决:根据网页源代码进行多次练习和书写,熟练掌握一些符号的含义及使用方法。
二、爬取多页贴吧图片
多页贴吧与单页贴吧图片的爬取方法大致相同,只是在后者的基础上添加了一个for循环,用于遍历每个网页数据。
for i in range(1,6):
url='http://www.ivsky.com/bizhi/index_'+str(i)+'.html'
html = getHtml(url)
print getImg(html,i)
开始的时候主要是找到每个网页源代码的相同之处,然后写正则表示它们。
发现每次翻页后index_后的数字部分变化,使用字符串str(i)来表示,使用了range循环:
range(start, stop[, step])
start: 计数从 start 开始。默认是从 0 开始。例如range(5)等价于range(0, 5);
end: 计数到 end 结束,但不包括 end。例如:range(0, 5) 是[0, 1, 2, 3, 4]没有5
step:步长,默认为1。例如:range(0, 5) 等价于 range(0, 5, 1)
除了range循环的书写外还出现了保存爬取内容时发生图片覆盖问题。
x = 0+i*100
for imgurl in imglist:
urllib2.urlretrieve(imgurl, 'D:\999\%s.jpg' % x)
x += 1
通过对x的一个叠加,使每个图片保存时的名字不同,解决了因名称相同发生的覆盖。
三、爬取指定网站的图片及对应链接。
因为要爬取图片及对应链接,所以本任务采取的方法是先使用正则把自己所需部分匹配出来,也就是每张图片及对应链接部分。
e1list = re.findall('<div class="il_img"><a href=(.*?)</p>',text,re.S)
re.S。它表示“.”的作用扩展到整个字符串,包括“\n”。如果不使用re.S参数,则只在每一行内进行匹配,如果一行没有,就换下一行重新开始,不会跨行。而使用re.S参数以后,正则表达式会将这个字符串作为一个整体,将“\n”当做一个普通的字符加入到这个字符串中,在整体中进行匹配。
初次涉及新建一个文件然后写入爬取内容:
file = open('text.txt','w')
file.write(text2.encode('utf8'))
file.close()
其中还涉及了一个解码问题。一般来说,pycharm中使用的是Unicode编码,然后通过encode解码为utf-8编码。
encode()语法:
str.encode(encoding='UTF-8',errors='strict')
encoding -- 要使用的编码,如"UTF-8"。
errors -- 设置不同错误的处理方案。默认为 'strict',意为编码错误引起一个UnicodeError。 其他可能得值有 'ignore', 'replace', 'xmlcharrefreplace', 'backslashreplace' 以及通过 codecs.register_error() 注册的任何值。
还使用到了os模块,创建一个本地的目录。
saveDir = 'D:\picture\\'
# os.mkdirs(saveDir)
四、爬取糗事百科。
其中涉及到了对于消息头的使用
user_agent = 'Mozilla/4.0 (compatible; MSIE 5.5; Windows NT)'
headers = { 'User-Agent' : user_agent }
设置请求的身份,如果没有,服务器可能会没有响应
这个可以为任意的,没有每个网页规定的。通过后面的学习,知道网页该部分的查找位置在查看元素-网络中。
request = urllib2.Request(url=url, headers=headers)
response = urllib2.urlopen(request)
content = response.read().decode('utf-8')
pattern_author = re.compile(u'<h2>(.*?)</h2>',re.S)
pattern_content = re.compile(u'<div class="content">\n<span>\n\n\n(.*?)</span>',re.S)
pattern_comment = re.compile(u'<i class="number">(\d*)</i> 评论',re.S)
find_author = re.findall(pattern_author, content)+find_author
find_content = re.findall(pattern_content, content)+find_content
find_comment = re.findall(pattern_comment, content)+find_comment
当时是作为将作者、评论、时间进行分开爬取的,其中:
urllib2.urlopen(url[, data][, timeout]) #传url时候,用法同urllib里的urlopen II.1.1 它打开URL网址,url参数可以是一个字符串url或者是一个Request对象。可选的参数timeout,阻塞操作以秒为单位,如尝试连接(如果没有指定,将使用设置的全局默认timeout值)。urlopen方法也可通过建立了一个Request对象来明确指明想要获取的url。调用urlopen函数对请求的url返回一个response对象。这个response类似于一个file对象,所以用.read()函数可以操作这个response对象
compile(pattern [, flags]) ,该函数根据包含的正则表达式的字符串创建模式对象。可以实现更有效率的匹配。
decode的作用是将其他编码的字符串转换成unicode编码,如str1.decode('gb2312'),表示将gb2312编码的字符串str1转换成unicode编码。 encode的作用是将unicode编码转换成其他编码的字符串,如str2.encode('gb2312'),表示将unicode编码的字符串str2转换成gb2312编码。
五、bs4爬取小说
初步接触bs,最先看了bs的官方文档介绍,相对于正则表达式来说,bs要简单得多。
from bs4 import BeautifulSoup
soup = BeautifulSoup(content1,"html.parser")
ls1 = []
for tag1 in soup.find('div',class_="article_container").find_all('a'):
m_1 = tag1.get('href')
ls1.append(m_1)
相对于之前的任务,大概有以上部分的不同。Beautiful Soup自动将输入文档转换为Unicode编码,输出文档转换为utf-8编码。
Bs4是直接针对于源代码的标签进行匹配和操作,find函数只能找到匹配到的第一个内容,findall则可以匹配所有的内容。
除了之前涉及过utf-8编码,这里接触到了gb18030,所以就对所有的编码进行了一个整理:
GBK是在国家标准GB2312基础上扩容后兼容GB2312的标准,UTF-8编码的文字可以在各国各种支持UTF8字符集的浏览器上显示。GB 2312或GB 2312-80是一个简体中文字符集的中国国家标准 。GB 2312标准共收录6763个汉字。对他们的范围有了一个认识。
爬小说的时候遇到的最大的问题就是部分爬取内容出现乱码问题。
def getContent(url1):
request = urllib2.Request(url=url1)
response = urllib2.urlopen(request)
content1 = response.read().decode("gb18030")
soup = BeautifulSoup(content1, "html.parser")
其实最后的解决方法就是解码的时候是在开始读取页面内容的时候就进行解码,之前一直没有解决是一直执着于对提取之后的最后结果进行解码,至于为什么会这样,还有待考证。
还有一个后来学到的小知识,就是当使用标签直接提取文字时会出现换行很多,文字之间有很多空隙,很不整齐的情况,使用s=(k.get_text(strip=True))
就好了。
import sys
reload(sys)
sys.setdefaultencoding('utf-8')
默认的编码是ascii,当程序中出现非ascii编码时,python的处理常常会报错UnicodeDecodeError此时需要自己设置将python的默认编码,一般设置为utf8的编码格式。加入上面内容之后再执行就会发现编码已经被设置为utf8的了,但是在解释器里修改的编码只能保证当次有效。
六、将爬取的小说存入mysql。
特别需要注意的是需要存的代码的编码格式一定要与库的编码格式相同。
其实还是相当于有一个类似模板的内容:
import MySQLdb
conn = MySQLdb.connect(host='127.0.0.1', db='msl', user='root', passwd='zgy1314', charset='utf-8')
cur = conn.cursor() # 通过获取到的conn数据库的cursor方法创建游标
cur.execute()
conn.commit() # 提交数据进数据库
conn.close() # 关闭数据库连接
Host为主机的ip地址,db为自己的数据库的名字,user为使用者,passwd是自己设置的密码,charset为库中表格的编码方式。
for j in url1:
m_2,m_3 = getContent(j)
hot = "insert into xiaoshuo(title,content) values('%s','%s')" % (m_2, m_3)
这部分代码就是传值的过程,利用了一个多类型传值将小说的内容和标题传入数据库的表格中。一个固定的写法:
insert into 表名(字段名1,字段名2)values(值a1,值b1), (值a2,值b2),
首先就是需要确保与数据库已经建立连接,还有就是一些编码一致问题比较难处理些。
for x in imglist:
title = re.search(p,x, re.S).group(1)
hot = "insert into user(id,url) values(%d,'%s')" % (k,title)
涉及到一个group(1)的内容。
正则表达式中的三组括号把匹配结果分成三组
group() 同group(0)就是匹配正则表达式整体结果group(1) 列出第一个括号匹配部分,group(2) 列出第二个括号匹配部分,group(3) 列出第三个括号匹配部分。
有一个解决了很久的问题:
文字存入mysql数据库时出现Incorrect string value错误
解决方法:如果觉得其他位置都没有问题,那一定是存在编码问题。
①弄清自己的代码爬取结果是什么编码,可以使用print type() 语句查询。
②保证自己需要存入的内容编码方式与数据库设置的编码方式相同。
③如果不相同的话,可以新建一个数据库或者更改一下数据库的设置
七、json爬取京东评价。
首先,获取json格式的文件,我用的是百度浏览器。
打开评论网页,单击鼠标右键—查看元素—蓝色框内为所要查看的内容。右面的请求地址即为评论网页地址。
data0 = re.sub(u'^fetchJSON_comment98vv106813\(', '', html)
#re.sub即利用正则替换想替换的内容
reg1 = re.compile('\);')
data1 = reg1.sub('', data0)
利用正则替换掉不需要的内容,最后保留的结果应为一个字典。
data = json.loads(data1)
json.loads()解码字符串转换为python形式时格式很重要,在这里转换成字典形式。将开头和结尾去掉剩下一个字典,不可以使用正则直接匹配所需内容
for i in data['comments']:
productName = i['referenceName'].encode('utf-8')
file.write("商品全名:{}".format(productName)+'\n')
commentTime = i['creationTime'].encode('utf-8')
file.write("用户评论时间:{}".format(commentTime)+'\n')
content = i['content'].encode('utf-8')
file.write("用户评论内容:{}".format(content)+'\n')
因为返回的内容为一个规则的字典,所以爬取所需内容时就按照提取字典中的值的方法,即根据键值。
然后呢接触了一个新的格式化字符串的函数str.format()它通过{}和:来代替%。
一般我们常用的就是%s- 接受一个字符串并按照给定的参数格式化该字符串。
当然除此之外还有很多格式控制。
在json中我们通常使用的有两个函数:
编码:把一个Python对象编码转换成Json字符串 json.dumps()
解码:把Json格式字符串解码转换成Python对象 json.loads()
使用json时也发生了编码问题,看来对于爬虫来说编码还真是一个大问题。
八、ajax异步获取酒店信息
开始接触ajax时的确有点蒙,任务就是用ajax爬取post提取的网页。
url = 'http://hotel.elong.com/ajax/list/asyncsearch'
header={'Accept':'application/json, text/javascript, */*; q=0.01',
'Accept-Encoding':'gzip, deflate',
'Accept-Language' :'zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3',
'Connection': 'keep-alive',
'Content-Type':'application/x-www-form-urlencoded;
'X-Requested-With':'XMLHttpRequest',
'Content-Length':'1062'}
data5 ={
'listRequest.orderFromID':50,
'listRequest.pageIndex':4}
file = open('juidian.txt','w')
首先,想要爬取post提取的网页时,就需要加上它特有的头和参数。然后就是翻页问题。要实现翻页,发现也输得变化值存在于参数中。于是写了以下代码来实现翻页:
for j in range(0,20):
data5['listRequest.pageIndex']=j
其实除了头和参数的变动之外,其他部分还是采取了json爬取的一些方法。
还有就是对于翻页之后每页的评论也需要进行翻页就显得有些困难。
后来发现在网址中有一些参数是对于整个网址的指向没有影响的,所以就有了一个长长的网址:
for s in range(0,1):
for k in data4:
url3 = 'http://hotel.elong.com/ajax/detail/gethotelreviews/?hotelId=' + str(
k) + '&recommendedType=0&pageIndex=' + str(
s) + '&mainTagId=0&subTagId=0&code=9253708&elongToken=j9cpvej8-4dea-4d07-a3b9-54b27a2797e9&ctripToken=88cf3b41-c4a2-4e49-a411-16af6b55ebec&_=1509281280799'
还有就是两个for循环套在一起需要注意的事项很多,还有一些对齐的内容真的很重要。稍有不慎就会满盘皆空。
在这次任务中还接触到了一个时间的函数:
import time
time.sleep(1)
time sleep() 函数推迟调用线程的运行。
r=requests.post(url,data=data5,headers=header).text
这里使用到了requests来发送json数据,除此之外还有很多,像发送post请求,get请求,上传文件等等。
做这个任务的过程中一直加载不出任何内容,结果最后发现是headers写错了。。
九、Scrapy
这个真的是说来话太长。
最先是看了scrapy的官方文档,安装成功之后就创建了一个新的工程。
然后里面自身就包含如下内容:
· scrapy.cfg: 项目的配置文件
· tutorial/: 该项目的python模块。之后您将在此加入代码。
· tutorial/items.py: 项目中的item文件.
· tutorial/pipelines.py: 项目中的pipelines文件.
· tutorial/settings.py: 项目的设置文件.
· tutorial/spiders/: 放置spider代码的目录.
首先定义一个item文件:
import scrapy
from scrapy import Item, Field
class yilongItem(scrapy.Item):
title = scrapy.Field()
contentnews = scrapy.Field()
pass
创建一个Spider,继承 scrapy.Spider 类, 定义以下三个属性:
· name: 用于区别Spider。 该名字必须是唯一的,您不可以为不同的Spider设定相同的名字。
· start_urls: 包含了Spider在启动时进行爬取的url列表。 因此,第一个被获取到的页面将是其中之一。 后续的URL则从初始的URL获取到的数据中提取。
· parse() 是spider的一个方法。 被调用时,每个初始URL完成下载后生成的 Response 对象将会作为唯一的参数传递给该函数。 该方法负责解析返回的数据(response data),提取数据(生成item)以及生成需要进一步处理的URL的 Request 对象。
然后在该目录下创建一个main.py文件,内容如下:
from scrapy import cmdline
user_agent = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_3) AppleWebKit/536.5 (KHTML, like Gecko) Chrome/19.0.1084.54 Safari/536.5'
cmdline.execute(['scrapy','crawl','yilong'])
自定义了一个py文件,我定义的是yilong.py。光是导入的就有很多很多:
# coding:utf-8
import scrapy
import requests
import json
import re
import urllib2
from bs4 import BeautifulSoup
from scrapy.http import Request
from scrapy.http import FormRequest
from tutorial.items import yilongItem
爬取的过程呢很多与ajax的相似之处,不同之处在这里:
item = yilongItem()
item['title'] = m1
item['contentnews']=m2
yield item
定义一个item,然后返回一个迭代的值使。用 yield 的时候就是一个迭代器,可以不断 yield 新的 request,如果用 return,就只会返回一个 request。
这里尝试过使用两个yield在一个程序中,不过没有成功。最后存入CSV文件时总是分开存入,所以又改成一个。
创建Pipelines.py文件,需写入:
class TutorialPipeline(object):
def process_item(self, item, spider):
return item
item (Item 对象) – 被爬取的item
spider (Spider 对象) – 爬取该item的spider
最最重要的settings.py文件,里面需要写入保存方式,保存地址和保存的内容的编码格式,以及制表的表头。
BOT_NAME = 'tutorial'
FEED_EXPORT_ENCODING = 'gbk'
FEED_URI = u'file///D:/tutorial/123456.csv'
FEED_FORMAT = 'csv'
FEED_EXPORTERS = {
'csv': 'tutorial.spiders.csv_item_exporter.MyProjectCsvItemExporter',
}
# CSV_DELIMITER = "\t" #制表符
FIELDS_TO_EXPORT = ['title',
'contentnews',
]
SPIDER_MODULES = ['tutorial.spiders']
NEWSPIDER_MODULE = 'tutorial.spiders'
有一个很重要的点也存在于该文件中,之前我的代码一直爬取不出文字内容,直到后来,
ROBOTSTXT_OBEY = False
需要爬取出内容时等号后应为False.
默认为True,就是要遵守robots.txt 的规则,那么 robots.txt 是什么?
robots.txt 是遵循 Robot协议 的一个文件,它保存在网站的服务器中,
它的作用是,告诉搜索引擎爬虫,本网站哪些目录下的网页 不希望 你进行爬取收录。
在Scrapy启动后,会在第一时间访问网站的 robots.txt 文件,然后决定该网站的爬取范围。
当然,并不是在做搜索引擎,而且在某些情况下我们想要获取的内容恰恰是被 robots.txt 所禁止访问的。