数据采集与融合技术 实验3

作业①:

  • 要求:指定一个网站,爬取这个网站中的所有的所有图片,例如中国气象网(http://www.weather.com.cn)。分别使用单线程和多线程的方式爬取。(限定爬取图片数量为学号后3位)

  • 输出信息:

    将下载的Url信息在控制台输出,并将下载的图片存储在images子文件夹中,并给出截图

1)、中国气象网图片爬取

  • 单线程爬取图片

-1.设置起始url和请求头,并用requests库进行解析

start_url = "http://www.weather.com.cn"
header = {
    "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/94.0.4606.71 Safari/537.36 Edg/94.0.992.38"}
# 用requests库解析url
r = requests.get(start_url, headers=header)
r.raise_for_status()
r.encoding = r.apparent_encoding
data = r.text

用BeautifulSoup方法解析data

soup = BeautifulSoup(data, "html.parser")
# select查询出带href属性的a结点
a = soup.select("a[href]")

其中经检查,a结点的href属性包含页面中的链接信息,如图:

-2.利用Beautifulsoup匹配出链接存入列表中后,设置函数进入链接利用正则表达式匹配出链接下所有的src数据

其中匹配src的函数代码如下

def searchsrc(link):
    header = {
        "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/94.0.4606.71 Safari/537.36 Edg/94.0.992.38"}
    # request方法解析link
    r = requests.get(link, headers=header)
    r.raise_for_status()
    r.encoding = r.apparent_encoding
    data = r.text
    soup = BeautifulSoup(data, "html.parser")
    title = soup.select("img")
    # 正则匹配src地址
    reg = r'.+?src="(\S+)"'
    srclist = []
    for i in title:
        src = re.findall(reg, str(i))
        srclist.append(src[0]) if src else ""
    return srclist

-3.编写Download函数对爬取出的src进行下载

    def download(link):
    global num
    file = "E:/pics/" + "No." + str(num+1) + ".jpg"  # file指先在指定文件夹里建立相关的文件夹才能爬取成功
    print("第" + str(num+1) + "张爬取成功")
    num += 1
    urllib.request.urlretrieve(url=link, filename=file)

-4.在最后的调用中对爬取数量进行限制

for link in links:
    # 设置爬取数量
    if num >= 413:
        break
    try:
        pics = searchsrc(link)
        for i in pics:
            download(i)
            if num >= 413:
                break
    except Exception as err:
        print(err)

控制台输出url结果如下:

最后在文件夹中查看结果如下:

  • 多线程爬取图片

-首先在单线程代码上修改为多线程。在控制台输出报错:远程主机中断连接,排查后发现应该是页面中的部分链接爬取失败。

-使用书上的代码,并将爬取链接的方式又beautifulsoup改为用正则匹配,这样过滤掉了一些有问题的链接

其中在主函数中设置线程,匹配src地址:

for image in images:
    try:
        src=image["src"]
        url=urllib.request.urljoin(start_url,src)
        # print(url)
        if url not in urls:
            # 设置if判断过滤.cn结尾的地址
            if not str(url).endswith('.cn'):
                if count == 413:
                    break
                # print(url)
                count = count+1
                # 调用threading类设置target为download函数
                T=threading.Thread(target=download,args=(url,count))
                # 设置线程为后台线程
                T.setDaemon(False)
                T.start()
                # 在threads队列中加入线程
                threads.append(T)

在download函数中写入本地文件:

def download(url,count):
    try:
        # 提取出图片结尾,如:.jpg,.png等
        if(url[len(url)-4]=="."):
            ext=url[len(url)-4:]
        else:
            ext=""
        req=urllib.request.Request(url,headers=headers)
        data=urllib.request.urlopen(req,timeout=100)
        data=data.read()
        # 写入本地文件
        fobj=open("E:\images\\"+str(count)+ext,"wb")
        fobj.write(data)
        fobj.close()
        print("downloaded "+str(count)+ext+"from "+str(url)+"\n")
    except Exception as err:
        print(err)

正则匹配过滤页面中链接(爬取前20个链接):

a = '<a href="(.*?)" '
links = re.findall(re.compile(a), str(soup))
# 遍历links过滤出url地址
for i in range(len(links)):
    if i < 20:
        links1.append(links[i]) if links[i] != "javascript:void(0);" else ""

控制台输出结果如下:

文件夹中结果如下:

作业1单线程码云链接

作业1多线程码云链接

2)、心得体会

作业1的单线程较为简单,和之前做过的作业类似,很快就完成了。对多线程的使用还是不够熟练,期间调试遇到了很多问题,其中在爬取时出现主机强迫关闭连接的报错信息:

最后发现是所爬取的页面图片总数不足,修改爬取链接的数量后解决

作业②:

  • 要求:使用scrapy框架复现作业①。

  • 输出信息:

    同作业①

1)、scrapy复现

-1.编写items.py,settings.py
items.py:

设置需传递给管道类的item

src = scrapy.Field()
name = scrapy.Field()

settings.py:

解除限制:

ROBOTSTXT_OBEY = False

打开pipeline:

ITEM_PIPELINES = {
    'weatherpics.pipelines.WeatherpicsPipeline':300,
}

-2.myspider.py

parse函数:

在parse函数中对www.weather.com.cn页面中的各个链接进行提取

a = soup.select("a[href]")
        item = WeatherpicsItem()
        links = []
        for link in a:
            links.append(link["href"]) if link["href"] != 'javascript:void(0)' else ""

如作业1中,编写searchsrc函数,利用正则表达式对各个页面中所包含的src地址进行提取:

def searchsrc(self,link):
    header = {
        "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/94.0.4606.71 Safari/537.36 Edg/94.0.992.38"}
    r = requests.get(link, headers=header)
    r.raise_for_status()
    r.encoding = r.apparent_encoding
    data = r.text
    soup = BeautifulSoup(data, "html.parser")
    title = soup.select("img")
    reg = r'.+?src="(\S+)"'
    srclist = []
    for i in title:
        src = re.findall(reg, str(i))
        srclist.append(src[0]) if src else ""
    return srclist

最后在parse函数中调用,并传递给pipeline:

for link in links:
    if num >= 413:
        break
    try:
        pics = self.searchsrc(link)
        for i in pics:
            item['src'] = i
            item['name'] = "E:/wtpics/" + "No." + str(num+1) + ".jpg"
            num += 1
            if num > 413:
                break
            yield item

-3.pipeline.py

在pipeline.py中调用urlretrieve方法进行图片下载保存

    def process_item(self, item, spider):
        link = item['src']
        file = item['name']
        urlretrieve(url=link, filename=file)
        print(str(file)+" download completed.")
        return item

结果如下:

作业2码云链接

2)、心得体会

在作业2上主要考察的是对scrapy框架的熟练运用,解决起来也比较顺利,在pipeline中下载图片时一开始是想要用pipeline自带的方法imagepipeline进行下载,但是发现其控制台输出信息较为混乱,最后还是用了熟悉的urlretrieve进行下载。

作业③:

  • 要求:爬取豆瓣电影数据使用scrapy和xpath,并将内容存储到数据库,同时将图片存储在

    imgs路径下。

  • 候选网站: https://movie.douban.com/top250

  • 输出信息:

    序号 电影名称 导演 演员 简介 电影评分 电影封面
    1 肖申克的救赎 弗兰克·德拉邦特 蒂姆·罗宾斯 希望让人自由 9.7 ./imgs/xsk.jpg
    2...

1)、豆瓣电影榜单爬取

-1.查看网页,发现排行页面显示主演信息不全(以及部分导演信息)

于是考虑进入每个电影的链接中,爬取出完整的演员信息

-2.编写settings类,items类

在settings类中先打开管道

ITEM_PIPELINES = {
'douban.pipelines.DoubanPipeline': 300,
}

解除限制:

ROBOTSTXT_OBEY = False

item类:

title = scrapy.Field()
score = scrapy.Field()
content = scrapy.Field()
rank = scrapy.Field()
src = scrapy.Field()
director = scrapy.Field()
actors = scrapy.Field()

-3.编写myspider.py

parse函数:

解析html

dammit = UnicodeDammit(response.body, ["utf-8", "gbk"])
            data = dammit.unicode_markup
            selector = scrapy.Selector(text=data)

在排行榜页面,爬取简介,电影名称和进入详细信息页面的链接

如图,需爬取的数据都在class='item'下,对该结点进行遍历:

for li in selector.xpath("//div[@class='item']"):
    item['title'] = li.xpath("./div[@class='info']/"
            "div[@class='hd']/a/span[@class='title']/text()").extract_first()
    item['content'] = li.xpath("./div[@class='info']/div[@class='bd']/p[position()=2]/span/text()").extract_first()
    # xpath提取出每个电影的后续链接
    link = li.xpath("./div[@class='info']/div[@class='hd']/a/@href").extract_first()
    # 设置request请求头

爬取到link之后用requests方法解析该链接的html(老师说再用scrapy解析会比较复杂):

header = {
    'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/95.0.4638.54 Safari/537.36 Edg/95.0.1020.30',
}
r = requests.get(link, headers=header)
r.raise_for_status()
# 设置解码格式为utf8防止出现乱码
r.encoding = 'utf8'

没有设置r.encoding为'utf8'时,爬取会出现乱码

其中需爬取的演员,导演等内容均在div[info]的span结点下,利用xpath进行爬取,并赋值给item:
在网页检查需爬取数据:

dir = info.xpath(
    "./span[@class='pl']/following-sibling::span[1]/a[@rel='v:directedBy']/text()").extract()
item['director'] = "/".join(dir)
actor = info.xpath(
    "./span[@class='pl']/following-sibling::span[1]/a[@rel='v:starring']/text()").extract()
item['actors'] = "/".join(actor)
item['score'] = selector2.xpath("//strong[@class='ll rating_num']/text()").extract_first()
item['src'] = selector2.xpath("//a[@class='nbgnbg']/img/@src").extract_first()
item['rank'] = selector2.xpath("//span[@class='top250-no']/text()").extract_first().split(".")[1]
yield item

在parse函数的最后设置翻页处理:

page = selector.xpath("//div[@class='paginator']/span[@class='thispage']/following-sibling::a[1]/@href").extract_first()
link_nextpage = "https://movie.douban.com/top250"+page
if page:
    url = response.urljoin(link_nextpage)
    yield scrapy.Request(url=url, callback=self.parse, dont_filter=True)

-4.编写pipeline.py

在pipeline中主要是进行将数据存入数据库中以及下载电影封面

连接mysql数据库:

def open_spider(self,spider):
    try:
        # 连接mysql数据库
        self.con = pymysql.connect(host="127.0.0.1", port=3306, user="root",
        password = "hts2953936", database = "mydb", charset = "utf8")
        self.cursor = self.con.cursor(pymysql.cursors.DictCursor)# 设置cursor
        self.cursor.execute("delete from doubanmovie_copy2")
        self.opened = True
    except Exception as err:
    	self.opened = False

process_item函数:

调用urlretrieve函数下载电影封面

    picpath = "E:/imgs/"+ str(item['title']) +".jpg"
    # 调用urlretrieve函数下载图片
    urlretrieve(url=item['src'],filename=picpath)
    print(str(picpath)+" download completed")

mysql语句将数据插入数据库

print("insert into doubanmovie_copy2 (Mrank,Mtitle,Mdirector,Mactor,Mscore,Mcontent,Mposter) values (%s,%s,%s,%s,%s,%s,%s)",
(item["rank"],item["title"], item['director'], item["actors"], item['score'], item['content'],str(picpath)))
if self.opened:
    self.cursor.execute(
    "insert into doubanmovie_copy2 (Mrank,Mtitle,Mdirector,Mactor,Mscore,Mcontent,Mposter) values (%s,%s,%s,%s,%s,%s,%s)",
    (item["rank"],item["title"], item['director'], item["actors"], item['score'], item['content'],str(picpath)))

在插入数据库时遇到了一些困难:

(1)一开始插入时发现多条数据插入报错,原因是建表时varchar()字段设置的不够大,最后将几个较长的属性改为text格式解决

(2)插入Mrank是一开始设置插入%d字段报错,最后发现插入数据库时会自动修正对应的数据类型解决了。

(3)全部插入后发现缺少了第76和第137条数据,检查后发现是actors字段和content字段后含有单引号,最后改变values字段解决了

-4.编写run,运行

from scrapy import cmdline
cmdline.execute("scrapy crawl myspider -s LOG_ENABLED=True".split())

控制台输出如下:

图片爬取到文件夹中:

数据存入数据库中,在navicat查看:

全部数据爬取成功:

作业3码云链接

2)、心得体会

做作业3时为了爬取到完整的演员信息,需要爬取到每个电影的链接后再对链接进行解析。在解析网页的时候,除了没有指定编码格式爬取到乱码之外,没有遇到很大的困难,主要是卡在插入数据库这一块上,对于数据库的操作还不够熟练。因为需要多次调试,豆瓣经常检测到ip异常行为:

这次作业又比较特殊,需要爬取到页面中的link再进行解析,储存到本地来调试的方法行不通了,所以以后还是要将代码好好完善后再进行调试。

posted @ 2021-10-31 09:52  paulncle  阅读(55)  评论(0编辑  收藏  举报