diff --git a/project/.idea/.gitignore b/project/.idea/.gitignore deleted file mode 100644 index b6b1ecf..0000000 --- a/project/.idea/.gitignore +++ /dev/null @@ -1,10 +0,0 @@ -# 默认忽略的文件 -/shelf/ -/workspace.xml -# 已忽略包含查询文件的默认文件夹 -/queries/ -# Datasource local storage ignored files -/dataSources/ -/dataSources.local.xml -# 基于编辑器的 HTTP 客户端请求 -/httpRequests/ diff --git a/project/.idea/compiler.xml b/project/.idea/compiler.xml deleted file mode 100644 index 90212ab..0000000 --- a/project/.idea/compiler.xml +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - - - - - \ No newline at end of file diff --git a/project/.idea/encodings.xml b/project/.idea/encodings.xml deleted file mode 100644 index aa00ffa..0000000 --- a/project/.idea/encodings.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - \ No newline at end of file diff --git a/project/.idea/jarRepositories.xml b/project/.idea/jarRepositories.xml deleted file mode 100644 index 712ab9d..0000000 --- a/project/.idea/jarRepositories.xml +++ /dev/null @@ -1,20 +0,0 @@ - - - - - - - - - - - \ No newline at end of file diff --git a/project/.idea/misc.xml b/project/.idea/misc.xml deleted file mode 100644 index 9dc782b..0000000 --- a/project/.idea/misc.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/project/.idea/vcs.xml b/project/.idea/vcs.xml deleted file mode 100644 index 6c0b863..0000000 --- a/project/.idea/vcs.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/project/202506050211-靖佳颖-期末实验报告.docx b/project/202506050211-靖佳颖-期末实验报告.docx deleted file mode 100644 index 5e5c363..0000000 Binary files a/project/202506050211-靖佳颖-期末实验报告.docx and /dev/null differ diff --git a/project/charts/news_time_trend.png b/project/charts/news_time_trend.png deleted file mode 100644 index caa1bf0..0000000 Binary files a/project/charts/news_time_trend.png and /dev/null differ diff --git a/project/charts/news_top_words.png b/project/charts/news_top_words.png deleted file mode 100644 index efa2fda..0000000 Binary files a/project/charts/news_top_words.png and /dev/null differ diff --git a/project/charts/price_histogram.png b/project/charts/price_histogram.png deleted file mode 100644 index 2e8a277..0000000 Binary files a/project/charts/price_histogram.png and /dev/null differ diff --git a/project/charts/province_bar.png b/project/charts/province_bar.png deleted file mode 100644 index 08ad8b7..0000000 Binary files a/project/charts/province_bar.png and /dev/null differ diff --git a/project/charts/rating_pie.png b/project/charts/rating_pie.png deleted file mode 100644 index 20e8426..0000000 Binary files a/project/charts/rating_pie.png and /dev/null differ diff --git a/project/charts/temperature_comparison.png b/project/charts/temperature_comparison.png deleted file mode 100644 index d006d51..0000000 Binary files a/project/charts/temperature_comparison.png and /dev/null differ diff --git a/project/charts/temperature_上海.png b/project/charts/temperature_上海.png deleted file mode 100644 index e26c047..0000000 Binary files a/project/charts/temperature_上海.png and /dev/null differ diff --git a/project/charts/temperature_北京.png b/project/charts/temperature_北京.png deleted file mode 100644 index ea333a1..0000000 Binary files a/project/charts/temperature_北京.png and /dev/null differ diff --git a/project/charts/temperature_广州.png b/project/charts/temperature_广州.png deleted file mode 100644 index e2e9759..0000000 Binary files a/project/charts/temperature_广州.png and /dev/null differ diff --git a/project/dependency-reduced-pom.xml b/project/dependency-reduced-pom.xml deleted file mode 100644 index 6997e2b..0000000 --- a/project/dependency-reduced-pom.xml +++ /dev/null @@ -1,64 +0,0 @@ - - - 4.0.0 - com.example - crawler-project - crawler-project - 1.0.0 - Java爬虫项目 - MVC + Command + Strategy模式 - - - - maven-compiler-plugin - 3.11.0 - - ${java.version} - ${java.version} - ${project.build.sourceEncoding} - - - - maven-shade-plugin - 3.5.0 - - - package - - shade - - - - - com.example.crawler.Main - - - - - - - - - - - junit - junit - 4.13.2 - test - - - hamcrest-core - org.hamcrest - - - - - - 11 - 11 - 1.17.2 - 1.5.3 - 11 - 2.10.1 - UTF-8 - - diff --git a/project/output/books_20260530_190333.json b/project/output/books_20260530_190333.json deleted file mode 100644 index e715ee6..0000000 --- a/project/output/books_20260530_190333.json +++ /dev/null @@ -1,3602 +0,0 @@ -[ - { - "title": "A Light in the Attic", - "price": "£51.77", - "availability": "In stock", - "rating": "3星" - }, - { - "title": "Tipping the Velvet", - "price": "£53.74", - "availability": "In stock", - "rating": "1星" - }, - { - "title": "Soumission", - "price": "£50.10", - "availability": "In stock", - "rating": "1星" - }, - { - "title": "Sharp Objects", - "price": "£47.82", - "availability": "In stock", - "rating": "4星" - }, - { - "title": "Sapiens: A Brief History of Humankind", - "price": "£54.23", - "availability": "In stock", - "rating": "5星" - }, - { - "title": "The Requiem Red", - "price": "£22.65", - "availability": "In stock", - "rating": "1星" - }, - { - "title": "The Dirty Little Secrets of Getting Your Dream Job", - "price": "£33.34", - "availability": "In stock", - "rating": "4星" - }, - { - "title": "The Coming Woman: A Novel Based on the Life of the Infamous Feminist, Victoria Woodhull", - "price": "£17.93", - "availability": "In stock", - "rating": "3星" - }, - { - "title": "The Boys in the Boat: Nine Americans and Their Epic Quest for Gold at the 1936 Berlin Olympics", - "price": "£22.60", - "availability": "In stock", - "rating": "4星" - }, - { - "title": "The Black Maria", - "price": "£52.15", - "availability": "In stock", - "rating": "1星" - }, - { - "title": "Starving Hearts (Triangular Trade Trilogy, #1)", - "price": "£13.99", - "availability": "In stock", - "rating": "2星" - }, - { - "title": "Shakespeare's Sonnets", - "price": "£20.66", - "availability": "In stock", - "rating": "4星" - }, - { - "title": "Set Me Free", - "price": "£17.46", - "availability": "In stock", - "rating": "5星" - }, - { - "title": "Scott Pilgrim's Precious Little Life (Scott Pilgrim #1)", - "price": "£52.29", - "availability": "In stock", - "rating": "5星" - }, - { - "title": "Rip it Up and Start Again", - "price": "£35.02", - "availability": "In stock", - "rating": "5星" - }, - { - "title": "Our Band Could Be Your Life: Scenes from the American Indie Underground, 1981-1991", - "price": "£57.25", - "availability": "In stock", - "rating": "3星" - }, - { - "title": "Olio", - "price": "£23.88", - "availability": "In stock", - "rating": "1星" - }, - { - "title": "Mesaerion: The Best Science Fiction Stories 1800-1849", - "price": "£37.59", - "availability": "In stock", - "rating": "1星" - }, - { - "title": "Libertarianism for Beginners", - "price": "£51.33", - "availability": "In stock", - "rating": "2星" - }, - { - "title": "It's Only the Himalayas", - "price": "£45.17", - "availability": "In stock", - "rating": "2星" - }, - { - "title": "In Her Wake", - "price": "£12.84", - "availability": "In stock", - "rating": "1星" - }, - { - "title": "How Music Works", - "price": "£37.32", - "availability": "In stock", - "rating": "2星" - }, - { - "title": "Foolproof Preserving: A Guide to Small Batch Jams, Jellies, Pickles, Condiments, and More: A Foolproof Guide to Making Small Batch Jams, Jellies, Pickles, Condiments, and More", - "price": "£30.52", - "availability": "In stock", - "rating": "3星" - }, - { - "title": "Chase Me (Paris Nights #2)", - "price": "£25.27", - "availability": "In stock", - "rating": "5星" - }, - { - "title": "Black Dust", - "price": "£34.53", - "availability": "In stock", - "rating": "5星" - }, - { - "title": "Birdsong: A Story in Pictures", - "price": "£54.64", - "availability": "In stock", - "rating": "3星" - }, - { - "title": "America's Cradle of Quarterbacks: Western Pennsylvania's Football Factory from Johnny Unitas to Joe Montana", - "price": "£22.50", - "availability": "In stock", - "rating": "3星" - }, - { - "title": "Aladdin and His Wonderful Lamp", - "price": "£53.13", - "availability": "In stock", - "rating": "3星" - }, - { - "title": "Worlds Elsewhere: Journeys Around Shakespeare’s Globe", - "price": "£40.30", - "availability": "In stock", - "rating": "5星" - }, - { - "title": "Wall and Piece", - "price": "£44.18", - "availability": "In stock", - "rating": "4星" - }, - { - "title": "The Four Agreements: A Practical Guide to Personal Freedom", - "price": "£17.66", - "availability": "In stock", - "rating": "5星" - }, - { - "title": "The Five Love Languages: How to Express Heartfelt Commitment to Your Mate", - "price": "£31.05", - "availability": "In stock", - "rating": "3星" - }, - { - "title": "The Elephant Tree", - "price": "£23.82", - "availability": "In stock", - "rating": "5星" - }, - { - "title": "The Bear and the Piano", - "price": "£36.89", - "availability": "In stock", - "rating": "1星" - }, - { - "title": "Sophie's World", - "price": "£15.94", - "availability": "In stock", - "rating": "5星" - }, - { - "title": "Penny Maybe", - "price": "£33.29", - "availability": "In stock", - "rating": "3星" - }, - { - "title": "Maude (1883-1993):She Grew Up with the country", - "price": "£18.02", - "availability": "In stock", - "rating": "2星" - }, - { - "title": "In a Dark, Dark Wood", - "price": "£19.63", - "availability": "In stock", - "rating": "1星" - }, - { - "title": "Behind Closed Doors", - "price": "£52.22", - "availability": "In stock", - "rating": "4星" - }, - { - "title": "You can't bury them all: Poems", - "price": "£33.63", - "availability": "In stock", - "rating": "2星" - }, - { - "title": "Slow States of Collapse: Poems", - "price": "£57.31", - "availability": "In stock", - "rating": "3星" - }, - { - "title": "Reasons to Stay Alive", - "price": "£26.41", - "availability": "In stock", - "rating": "2星" - }, - { - "title": "Private Paris (Private #10)", - "price": "£47.61", - "availability": "In stock", - "rating": "5星" - }, - { - "title": "#HigherSelfie: Wake Up Your Life. Free Your Soul. Find Your Tribe.", - "price": "£23.11", - "availability": "In stock", - "rating": "5星" - }, - { - "title": "Without Borders (Wanderlove #1)", - "price": "£45.07", - "availability": "In stock", - "rating": "2星" - }, - { - "title": "When We Collided", - "price": "£31.77", - "availability": "In stock", - "rating": "1星" - }, - { - "title": "We Love You, Charlie Freeman", - "price": "£50.27", - "availability": "In stock", - "rating": "5星" - }, - { - "title": "Untitled Collection: Sabbath Poems 2014", - "price": "£14.27", - "availability": "In stock", - "rating": "4星" - }, - { - "title": "Unseen City: The Majesty of Pigeons, the Discreet Charm of Snails & Other Wonders of the Urban Wilderness", - "price": "£44.18", - "availability": "In stock", - "rating": "4星" - }, - { - "title": "Unicorn Tracks", - "price": "£18.78", - "availability": "In stock", - "rating": "3星" - }, - { - "title": "Unbound: How Eight Technologies Made Us Human, Transformed Society, and Brought Our World to the Brink", - "price": "£25.52", - "availability": "In stock", - "rating": "1星" - }, - { - "title": "Tsubasa: WoRLD CHRoNiCLE 2 (Tsubasa WoRLD CHRoNiCLE #2)", - "price": "£16.28", - "availability": "In stock", - "rating": "1星" - }, - { - "title": "Throwing Rocks at the Google Bus: How Growth Became the Enemy of Prosperity", - "price": "£31.12", - "availability": "In stock", - "rating": "3星" - }, - { - "title": "This One Summer", - "price": "£19.49", - "availability": "In stock", - "rating": "4星" - }, - { - "title": "Thirst", - "price": "£17.27", - "availability": "In stock", - "rating": "5星" - }, - { - "title": "The Torch Is Passed: A Harding Family Story", - "price": "£19.09", - "availability": "In stock", - "rating": "1星" - }, - { - "title": "The Secret of Dreadwillow Carse", - "price": "£56.13", - "availability": "In stock", - "rating": "1星" - }, - { - "title": "The Pioneer Woman Cooks: Dinnertime: Comfort Classics, Freezer Food, 16-Minute Meals, and Other Delicious Ways to Solve Supper!", - "price": "£56.41", - "availability": "In stock", - "rating": "1星" - }, - { - "title": "The Past Never Ends", - "price": "£56.50", - "availability": "In stock", - "rating": "4星" - }, - { - "title": "The Natural History of Us (The Fine Art of Pretending #2)", - "price": "£45.22", - "availability": "In stock", - "rating": "3星" - }, - { - "title": "The Nameless City (The Nameless City #1)", - "price": "£38.16", - "availability": "In stock", - "rating": "4星" - }, - { - "title": "The Murder That Never Was (Forensic Instincts #5)", - "price": "£54.11", - "availability": "In stock", - "rating": "3星" - }, - { - "title": "The Most Perfect Thing: Inside (and Outside) a Bird's Egg", - "price": "£42.96", - "availability": "In stock", - "rating": "4星" - }, - { - "title": "The Mindfulness and Acceptance Workbook for Anxiety: A Guide to Breaking Free from Anxiety, Phobias, and Worry Using Acceptance and Commitment Therapy", - "price": "£23.89", - "availability": "In stock", - "rating": "4星" - }, - { - "title": "The Life-Changing Magic of Tidying Up: The Japanese Art of Decluttering and Organizing", - "price": "£16.77", - "availability": "In stock", - "rating": "3星" - }, - { - "title": "The Inefficiency Assassin: Time Management Tactics for Working Smarter, Not Longer", - "price": "£20.59", - "availability": "In stock", - "rating": "5星" - }, - { - "title": "The Gutsy Girl: Escapades for Your Life of Epic Adventure", - "price": "£37.13", - "availability": "In stock", - "rating": "1星" - }, - { - "title": "The Electric Pencil: Drawings from Inside State Hospital No. 3", - "price": "£56.06", - "availability": "In stock", - "rating": "1星" - }, - { - "title": "The Death of Humanity: and the Case for Life", - "price": "£58.11", - "availability": "In stock", - "rating": "4星" - }, - { - "title": "The Bulletproof Diet: Lose up to a Pound a Day, Reclaim Energy and Focus, Upgrade Your Life", - "price": "£49.05", - "availability": "In stock", - "rating": "3星" - }, - { - "title": "The Art Forger", - "price": "£40.76", - "availability": "In stock", - "rating": "3星" - }, - { - "title": "The Age of Genius: The Seventeenth Century and the Birth of the Modern Mind", - "price": "£19.73", - "availability": "In stock", - "rating": "1星" - }, - { - "title": "The Activist's Tao Te Ching: Ancient Advice for a Modern Revolution", - "price": "£32.24", - "availability": "In stock", - "rating": "5星" - }, - { - "title": "Spark Joy: An Illustrated Master Class on the Art of Organizing and Tidying Up", - "price": "£41.83", - "availability": "In stock", - "rating": "4星" - }, - { - "title": "Soul Reader", - "price": "£39.58", - "availability": "In stock", - "rating": "2星" - }, - { - "title": "Security", - "price": "£39.25", - "availability": "In stock", - "rating": "2星" - }, - { - "title": "Saga, Volume 6 (Saga (Collected Editions) #6)", - "price": "£25.02", - "availability": "In stock", - "rating": "3星" - }, - { - "title": "Saga, Volume 5 (Saga (Collected Editions) #5)", - "price": "£51.04", - "availability": "In stock", - "rating": "2星" - }, - { - "title": "Reskilling America: Learning to Labor in the Twenty-First Century", - "price": "£19.83", - "availability": "In stock", - "rating": "2星" - }, - { - "title": "Rat Queens, Vol. 3: Demons (Rat Queens (Collected Editions) #11-15)", - "price": "£50.40", - "availability": "In stock", - "rating": "3星" - }, - { - "title": "Princess Jellyfish 2-in-1 Omnibus, Vol. 01 (Princess Jellyfish 2-in-1 Omnibus #1)", - "price": "£13.61", - "availability": "In stock", - "rating": "5星" - }, - { - "title": "Princess Between Worlds (Wide-Awake Princess #5)", - "price": "£13.34", - "availability": "In stock", - "rating": "5星" - }, - { - "title": "Pop Gun War, Volume 1: Gift", - "price": "£18.97", - "availability": "In stock", - "rating": "1星" - }, - { - "title": "Political Suicide: Missteps, Peccadilloes, Bad Calls, Backroom Hijinx, Sordid Pasts, Rotten Breaks, and Just Plain Dumb Mistakes in the Annals of American Politics", - "price": "£36.28", - "availability": "In stock", - "rating": "2星" - }, - { - "title": "Patience", - "price": "£10.16", - "availability": "In stock", - "rating": "3星" - }, - { - "title": "Outcast, Vol. 1: A Darkness Surrounds Him (Outcast #1)", - "price": "£15.44", - "availability": "In stock", - "rating": "4星" - }, - { - "title": "orange: The Complete Collection 1 (orange: The Complete Collection #1)", - "price": "£48.41", - "availability": "In stock", - "rating": "1星" - }, - { - "title": "Online Marketing for Busy Authors: A Step-By-Step Guide", - "price": "£46.35", - "availability": "In stock", - "rating": "1星" - }, - { - "title": "On a Midnight Clear", - "price": "£14.07", - "availability": "In stock", - "rating": "3星" - }, - { - "title": "Obsidian (Lux #1)", - "price": "£14.86", - "availability": "In stock", - "rating": "2星" - }, - { - "title": "My Paris Kitchen: Recipes and Stories", - "price": "£33.37", - "availability": "In stock", - "rating": "2星" - }, - { - "title": "Masks and Shadows", - "price": "£56.40", - "availability": "In stock", - "rating": "2星" - }, - { - "title": "Mama Tried: Traditional Italian Cooking for the Screwed, Crude, Vegan, and Tattooed", - "price": "£14.02", - "availability": "In stock", - "rating": "4星" - }, - { - "title": "Lumberjanes, Vol. 2: Friendship to the Max (Lumberjanes #5-8)", - "price": "£46.91", - "availability": "In stock", - "rating": "2星" - }, - { - "title": "Lumberjanes, Vol. 1: Beware the Kitten Holy (Lumberjanes #1-4)", - "price": "£45.61", - "availability": "In stock", - "rating": "3星" - }, - { - "title": "Lumberjanes Vol. 3: A Terrible Plan (Lumberjanes #9-12)", - "price": "£19.92", - "availability": "In stock", - "rating": "2星" - }, - { - "title": "Layered: Baking, Building, and Styling Spectacular Cakes", - "price": "£40.11", - "availability": "In stock", - "rating": "1星" - }, - { - "title": "Judo: Seven Steps to Black Belt (an Introductory Guide for Beginners)", - "price": "£53.90", - "availability": "In stock", - "rating": "2星" - }, - { - "title": "Join", - "price": "£35.67", - "availability": "In stock", - "rating": "5星" - }, - { - "title": "In the Country We Love: My Family Divided", - "price": "£22.00", - "availability": "In stock", - "rating": "4星" - }, - { - "title": "Immunity: How Elie Metchnikoff Changed the Course of Modern Medicine", - "price": "£57.36", - "availability": "In stock", - "rating": "5星" - }, - { - "title": "I Hate Fairyland, Vol. 1: Madly Ever After (I Hate Fairyland (Compilations) #1-5)", - "price": "£29.17", - "availability": "In stock", - "rating": "2星" - }, - { - "title": "I am a Hero Omnibus Volume 1", - "price": "£54.63", - "availability": "In stock", - "rating": "3星" - }, - { - "title": "How to Be Miserable: 40 Strategies You Already Use", - "price": "£46.03", - "availability": "In stock", - "rating": "1星" - }, - { - "title": "Her Backup Boyfriend (The Sorensen Family #1)", - "price": "£33.97", - "availability": "In stock", - "rating": "1星" - }, - { - "title": "Giant Days, Vol. 2 (Giant Days #5-8)", - "price": "£22.11", - "availability": "In stock", - "rating": "2星" - }, - { - "title": "Forever and Forever: The Courtship of Henry Longfellow and Fanny Appleton", - "price": "£29.69", - "availability": "In stock", - "rating": "3星" - }, - { - "title": "First and First (Five Boroughs #3)", - "price": "£15.97", - "availability": "In stock", - "rating": "4星" - }, - { - "title": "Fifty Shades Darker (Fifty Shades #2)", - "price": "£21.96", - "availability": "In stock", - "rating": "1星" - }, - { - "title": "Everydata: The Misinformation Hidden in the Little Data You Consume Every Day", - "price": "£54.35", - "availability": "In stock", - "rating": "2星" - }, - { - "title": "Don't Be a Jerk: And Other Practical Advice from Dogen, Japan's Greatest Zen Master", - "price": "£37.97", - "availability": "In stock", - "rating": "2星" - }, - { - "title": "Danganronpa Volume 1", - "price": "£51.99", - "availability": "In stock", - "rating": "4星" - }, - { - "title": "Crown of Midnight (Throne of Glass #2)", - "price": "£43.29", - "availability": "In stock", - "rating": "3星" - }, - { - "title": "Codename Baboushka, Volume 1: The Conclave of Death", - "price": "£36.72", - "availability": "In stock", - "rating": "4星" - }, - { - "title": "Camp Midnight", - "price": "£17.08", - "availability": "In stock", - "rating": "4星" - }, - { - "title": "Call the Nurse: True Stories of a Country Nurse on a Scottish Isle", - "price": "£29.14", - "availability": "In stock", - "rating": "5星" - }, - { - "title": "Burning", - "price": "£28.81", - "availability": "In stock", - "rating": "3星" - }, - { - "title": "Bossypants", - "price": "£49.46", - "availability": "In stock", - "rating": "2星" - }, - { - "title": "Bitch Planet, Vol. 1: Extraordinary Machine (Bitch Planet (Collected Editions))", - "price": "£37.92", - "availability": "In stock", - "rating": "2星" - }, - { - "title": "Avatar: The Last Airbender: Smoke and Shadow, Part 3 (Smoke and Shadow #3)", - "price": "£28.09", - "availability": "In stock", - "rating": "2星" - }, - { - "title": "Algorithms to Live By: The Computer Science of Human Decisions", - "price": "£30.81", - "availability": "In stock", - "rating": "1星" - }, - { - "title": "A World of Flavor: Your Gluten Free Passport", - "price": "£42.95", - "availability": "In stock", - "rating": "1星" - }, - { - "title": "A Piece of Sky, a Grain of Rice: A Memoir in Four Meditations", - "price": "£56.76", - "availability": "In stock", - "rating": "5星" - }, - { - "title": "A Murder in Time", - "price": "£16.64", - "availability": "In stock", - "rating": "1星" - }, - { - "title": "A Flight of Arrows (The Pathfinders #2)", - "price": "£55.53", - "availability": "In stock", - "rating": "5星" - }, - { - "title": "A Fierce and Subtle Poison", - "price": "£28.13", - "availability": "In stock", - "rating": "4星" - }, - { - "title": "A Court of Thorns and Roses (A Court of Thorns and Roses #1)", - "price": "£52.37", - "availability": "In stock", - "rating": "1星" - }, - { - "title": "(Un)Qualified: How God Uses Broken People to Do Big Things", - "price": "£54.00", - "availability": "In stock", - "rating": "5星" - }, - { - "title": "You Are What You Love: The Spiritual Power of Habit", - "price": "£21.87", - "availability": "In stock", - "rating": "4星" - }, - { - "title": "William Shakespeare's Star Wars: Verily, A New Hope (William Shakespeare's Star Wars #4)", - "price": "£43.30", - "availability": "In stock", - "rating": "4星" - }, - { - "title": "Tuesday Nights in 1980", - "price": "£21.04", - "availability": "In stock", - "rating": "2星" - }, - { - "title": "Tracing Numbers on a Train", - "price": "£41.60", - "availability": "In stock", - "rating": "3星" - }, - { - "title": "Throne of Glass (Throne of Glass #1)", - "price": "£35.07", - "availability": "In stock", - "rating": "3星" - }, - { - "title": "Thomas Jefferson and the Tripoli Pirates: The Forgotten War That Changed American History", - "price": "£59.64", - "availability": "In stock", - "rating": "1星" - }, - { - "title": "Thirteen Reasons Why", - "price": "£52.72", - "availability": "In stock", - "rating": "1星" - }, - { - "title": "The White Cat and the Monk: A Retelling of the Poem “Pangur Bán”", - "price": "£58.08", - "availability": "In stock", - "rating": "4星" - }, - { - "title": "The Wedding Dress", - "price": "£24.12", - "availability": "In stock", - "rating": "1星" - }, - { - "title": "The Vacationers", - "price": "£42.15", - "availability": "In stock", - "rating": "4星" - }, - { - "title": "The Third Wave: An Entrepreneur’s Vision of the Future", - "price": "£12.61", - "availability": "In stock", - "rating": "5星" - }, - { - "title": "The Stranger", - "price": "£17.44", - "availability": "In stock", - "rating": "4星" - }, - { - "title": "The Shadow Hero (The Shadow Hero)", - "price": "£33.14", - "availability": "In stock", - "rating": "1星" - }, - { - "title": "The Secret (The Secret #1)", - "price": "£27.37", - "availability": "In stock", - "rating": "4星" - }, - { - "title": "The Regional Office Is Under Attack!", - "price": "£51.36", - "availability": "In stock", - "rating": "5星" - }, - { - "title": "The Psychopath Test: A Journey Through the Madness Industry", - "price": "£36.00", - "availability": "In stock", - "rating": "2星" - }, - { - "title": "The Project", - "price": "£10.65", - "availability": "In stock", - "rating": "1星" - }, - { - "title": "The Power of Now: A Guide to Spiritual Enlightenment", - "price": "£43.54", - "availability": "In stock", - "rating": "2星" - }, - { - "title": "The Omnivore's Dilemma: A Natural History of Four Meals", - "price": "£38.21", - "availability": "In stock", - "rating": "2星" - }, - { - "title": "The Nerdy Nummies Cookbook: Sweet Treats for the Geek in All of Us", - "price": "£37.34", - "availability": "In stock", - "rating": "5星" - }, - { - "title": "The Murder of Roger Ackroyd (Hercule Poirot #4)", - "price": "£44.10", - "availability": "In stock", - "rating": "4星" - }, - { - "title": "The Mistake (Off-Campus #2)", - "price": "£43.29", - "availability": "In stock", - "rating": "3星" - }, - { - "title": "The Matchmaker's Playbook (Wingmen Inc. #1)", - "price": "£55.85", - "availability": "In stock", - "rating": "1星" - }, - { - "title": "The Love and Lemons Cookbook: An Apple-to-Zucchini Celebration of Impromptu Cooking", - "price": "£37.60", - "availability": "In stock", - "rating": "2星" - }, - { - "title": "The Long Shadow of Small Ghosts: Murder and Memory in an American City", - "price": "£10.97", - "availability": "In stock", - "rating": "1星" - }, - { - "title": "The Kite Runner", - "price": "£41.82", - "availability": "In stock", - "rating": "4星" - }, - { - "title": "The House by the Lake", - "price": "£36.95", - "availability": "In stock", - "rating": "1星" - }, - { - "title": "The Glittering Court (The Glittering Court #1)", - "price": "£44.28", - "availability": "In stock", - "rating": "1星" - }, - { - "title": "The Girl on the Train", - "price": "£55.02", - "availability": "In stock", - "rating": "2星" - }, - { - "title": "The Genius of Birds", - "price": "£17.24", - "availability": "In stock", - "rating": "1星" - }, - { - "title": "The Emerald Mystery", - "price": "£23.15", - "availability": "In stock", - "rating": "2星" - }, - { - "title": "The Cookies & Cups Cookbook: 125+ sweet & savory recipes reminding you to Always Eat Dessert First", - "price": "£41.25", - "availability": "In stock", - "rating": "1星" - }, - { - "title": "The Bridge to Consciousness: I'm Writing the Bridge Between Science and Our Old and New Beliefs.", - "price": "£32.00", - "availability": "In stock", - "rating": "3星" - }, - { - "title": "The Artist's Way: A Spiritual Path to Higher Creativity", - "price": "£38.49", - "availability": "In stock", - "rating": "5星" - }, - { - "title": "The Art of War", - "price": "£33.34", - "availability": "In stock", - "rating": "5星" - }, - { - "title": "The Argonauts", - "price": "£10.93", - "availability": "In stock", - "rating": "2星" - }, - { - "title": "The 10% Entrepreneur: Live Your Startup Dream Without Quitting Your Day Job", - "price": "£27.55", - "availability": "In stock", - "rating": "3星" - }, - { - "title": "Suddenly in Love (Lake Haven #1)", - "price": "£55.99", - "availability": "In stock", - "rating": "2星" - }, - { - "title": "Something More Than This", - "price": "£16.24", - "availability": "In stock", - "rating": "4星" - }, - { - "title": "Soft Apocalypse", - "price": "£26.12", - "availability": "In stock", - "rating": "2星" - }, - { - "title": "So You've Been Publicly Shamed", - "price": "£12.23", - "availability": "In stock", - "rating": "2星" - }, - { - "title": "Shoe Dog: A Memoir by the Creator of NIKE", - "price": "£23.99", - "availability": "In stock", - "rating": "2星" - }, - { - "title": "Shobu Samurai, Project Aryoku (#3)", - "price": "£29.06", - "availability": "In stock", - "rating": "3星" - }, - { - "title": "Secrets and Lace (Fatal Hearts #1)", - "price": "£20.27", - "availability": "In stock", - "rating": "1星" - }, - { - "title": "Scarlett Epstein Hates It Here", - "price": "£43.55", - "availability": "In stock", - "rating": "5星" - }, - { - "title": "Romero and Juliet: A Tragic Tale of Love and Zombies", - "price": "£36.94", - "availability": "In stock", - "rating": "1星" - }, - { - "title": "Redeeming Love", - "price": "£20.47", - "availability": "In stock", - "rating": "5星" - }, - { - "title": "Poses for Artists Volume 1 - Dynamic and Sitting Poses: An Essential Reference for Figure Drawing and the Human Form", - "price": "£41.06", - "availability": "In stock", - "rating": "1星" - }, - { - "title": "Poems That Make Grown Women Cry", - "price": "£14.19", - "availability": "In stock", - "rating": "4星" - }, - { - "title": "Nightingale, Sing", - "price": "£38.28", - "availability": "In stock", - "rating": "1星" - }, - { - "title": "Night Sky with Exit Wounds", - "price": "£41.05", - "availability": "In stock", - "rating": "1星" - }, - { - "title": "Mrs. Houdini", - "price": "£30.25", - "availability": "In stock", - "rating": "5星" - }, - { - "title": "Modern Romance", - "price": "£28.26", - "availability": "In stock", - "rating": "5星" - }, - { - "title": "Miss Peregrine’s Home for Peculiar Children (Miss Peregrine’s Peculiar Children #1)", - "price": "£10.76", - "availability": "In stock", - "rating": "1星" - }, - { - "title": "Louisa: The Extraordinary Life of Mrs. Adams", - "price": "£16.85", - "availability": "In stock", - "rating": "2星" - }, - { - "title": "Little Red", - "price": "£13.47", - "availability": "In stock", - "rating": "3星" - }, - { - "title": "Library of Souls (Miss Peregrine’s Peculiar Children #3)", - "price": "£48.56", - "availability": "In stock", - "rating": "5星" - }, - { - "title": "Large Print Heart of the Pride", - "price": "£19.15", - "availability": "In stock", - "rating": "2星" - }, - { - "title": "I Had a Nice Time And Other Lies...: How to find love & sh*t like that", - "price": "£57.36", - "availability": "In stock", - "rating": "4星" - }, - { - "title": "Hollow City (Miss Peregrine’s Peculiar Children #2)", - "price": "£42.98", - "availability": "In stock", - "rating": "1星" - }, - { - "title": "Grumbles", - "price": "£22.16", - "availability": "In stock", - "rating": "2星" - }, - { - "title": "Full Moon over Noah’s Ark: An Odyssey to Mount Ararat and Beyond", - "price": "£49.43", - "availability": "In stock", - "rating": "4星" - }, - { - "title": "Frostbite (Vampire Academy #2)", - "price": "£29.99", - "availability": "In stock", - "rating": "5星" - }, - { - "title": "Follow You Home", - "price": "£21.36", - "availability": "In stock", - "rating": "1星" - }, - { - "title": "First Steps for New Christians (Print Edition)", - "price": "£29.00", - "availability": "In stock", - "rating": "1星" - }, - { - "title": "Finders Keepers (Bill Hodges Trilogy #2)", - "price": "£53.53", - "availability": "In stock", - "rating": "5星" - }, - { - "title": "Fables, Vol. 1: Legends in Exile (Fables #1)", - "price": "£41.62", - "availability": "In stock", - "rating": "4星" - }, - { - "title": "Eureka Trivia 6.0", - "price": "£54.59", - "availability": "In stock", - "rating": "4星" - }, - { - "title": "Drive: The Surprising Truth About What Motivates Us", - "price": "£34.95", - "availability": "In stock", - "rating": "4星" - }, - { - "title": "Done Rubbed Out (Reightman & Bailey #1)", - "price": "£37.72", - "availability": "In stock", - "rating": "5星" - }, - { - "title": "Doing It Over (Most Likely To #1)", - "price": "£35.61", - "availability": "In stock", - "rating": "3星" - }, - { - "title": "Deliciously Ella Every Day: Quick and Easy Recipes for Gluten-Free Snacks, Packed Lunches, and Simple Meals", - "price": "£42.16", - "availability": "In stock", - "rating": "3星" - }, - { - "title": "Dark Notes", - "price": "£19.19", - "availability": "In stock", - "rating": "5星" - }, - { - "title": "Daring Greatly: How the Courage to Be Vulnerable Transforms the Way We Live, Love, Parent, and Lead", - "price": "£19.43", - "availability": "In stock", - "rating": "3星" - }, - { - "title": "Close to You", - "price": "£49.46", - "availability": "In stock", - "rating": "4星" - }, - { - "title": "Chasing Heaven: What Dying Taught Me About Living", - "price": "£37.80", - "availability": "In stock", - "rating": "2星" - }, - { - "title": "Big Magic: Creative Living Beyond Fear", - "price": "£30.80", - "availability": "In stock", - "rating": "3星" - }, - { - "title": "Becoming Wise: An Inquiry into the Mystery and Art of Living", - "price": "£27.43", - "availability": "In stock", - "rating": "2星" - }, - { - "title": "Beauty Restored (Riley Family Legacy Novellas #3)", - "price": "£11.11", - "availability": "In stock", - "rating": "2星" - }, - { - "title": "Batman: The Long Halloween (Batman)", - "price": "£36.50", - "availability": "In stock", - "rating": "2星" - }, - { - "title": "Batman: The Dark Knight Returns (Batman)", - "price": "£15.38", - "availability": "In stock", - "rating": "5星" - }, - { - "title": "Ayumi's Violin", - "price": "£15.48", - "availability": "In stock", - "rating": "2星" - }, - { - "title": "Anonymous", - "price": "£46.82", - "availability": "In stock", - "rating": "4星" - }, - { - "title": "Amy Meets the Saints and Sages", - "price": "£18.46", - "availability": "In stock", - "rating": "3星" - }, - { - "title": "Amid the Chaos", - "price": "£36.58", - "availability": "In stock", - "rating": "1星" - }, - { - "title": "Amatus", - "price": "£50.54", - "availability": "In stock", - "rating": "5星" - }, - { - "title": "Agnostic: A Spirited Manifesto", - "price": "£12.51", - "availability": "In stock", - "rating": "5星" - }, - { - "title": "Zealot: The Life and Times of Jesus of Nazareth", - "price": "£24.70", - "availability": "In stock", - "rating": "3星" - }, - { - "title": "You (You #1)", - "price": "£43.61", - "availability": "In stock", - "rating": "5星" - }, - { - "title": "Wonder Woman: Earth One, Volume One (Wonder Woman: Earth One #1)", - "price": "£37.34", - "availability": "In stock", - "rating": "4星" - }, - { - "title": "Wild Swans", - "price": "£14.36", - "availability": "In stock", - "rating": "2星" - }, - { - "title": "Why the Right Went Wrong: Conservatism--From Goldwater to the Tea Party and Beyond", - "price": "£52.65", - "availability": "In stock", - "rating": "4星" - }, - { - "title": "Whole Lotta Creativity Going On: 60 Fun and Unusual Exercises to Awaken and Strengthen Your Creativity", - "price": "£38.20", - "availability": "In stock", - "rating": "4星" - }, - { - "title": "What's It Like in Space?: Stories from Astronauts Who've Been There", - "price": "£19.60", - "availability": "In stock", - "rating": "2星" - }, - { - "title": "We Are Robin, Vol. 1: The Vigilante Business (We Are Robin #1)", - "price": "£53.90", - "availability": "In stock", - "rating": "1星" - }, - { - "title": "Walt Disney's Alice in Wonderland", - "price": "£12.96", - "availability": "In stock", - "rating": "5星" - }, - { - "title": "V for Vendetta (V for Vendetta Complete)", - "price": "£37.10", - "availability": "In stock", - "rating": "4星" - }, - { - "title": "Until Friday Night (The Field Party #1)", - "price": "£46.31", - "availability": "In stock", - "rating": "2星" - }, - { - "title": "Unbroken: A World War II Story of Survival, Resilience, and Redemption", - "price": "£45.95", - "availability": "In stock", - "rating": "2星" - }, - { - "title": "Twenty Yawns", - "price": "£22.08", - "availability": "In stock", - "rating": "2星" - }, - { - "title": "Through the Woods", - "price": "£25.38", - "availability": "In stock", - "rating": "2星" - }, - { - "title": "This Is Where It Ends", - "price": "£27.12", - "availability": "In stock", - "rating": "2星" - }, - { - "title": "The Year of Magical Thinking", - "price": "£43.04", - "availability": "In stock", - "rating": "2星" - }, - { - "title": "The Wright Brothers", - "price": "£56.80", - "availability": "In stock", - "rating": "4星" - }, - { - "title": "The White Queen (The Cousins' War #1)", - "price": "£25.91", - "availability": "In stock", - "rating": "5星" - }, - { - "title": "The Wedding Pact (The O'Malleys #2)", - "price": "£32.61", - "availability": "In stock", - "rating": "3星" - }, - { - "title": "The Time Keeper", - "price": "£27.88", - "availability": "In stock", - "rating": "5星" - }, - { - "title": "The Testament of Mary", - "price": "£52.67", - "availability": "In stock", - "rating": "4星" - }, - { - "title": "The Star-Touched Queen", - "price": "£46.02", - "availability": "In stock", - "rating": "5星" - }, - { - "title": "The Songs of the Gods", - "price": "£44.48", - "availability": "In stock", - "rating": "5星" - }, - { - "title": "The Song of Achilles", - "price": "£37.40", - "availability": "In stock", - "rating": "5星" - }, - { - "title": "The Rosie Project (Don Tillman #1)", - "price": "£54.04", - "availability": "In stock", - "rating": "1星" - }, - { - "title": "The Power of Habit: Why We Do What We Do in Life and Business", - "price": "£16.88", - "availability": "In stock", - "rating": "3星" - }, - { - "title": "The Marriage of Opposites", - "price": "£28.08", - "availability": "In stock", - "rating": "4星" - }, - { - "title": "The Lucifer Effect: Understanding How Good People Turn Evil", - "price": "£10.40", - "availability": "In stock", - "rating": "1星" - }, - { - "title": "The Long Haul (Diary of a Wimpy Kid #9)", - "price": "£44.07", - "availability": "In stock", - "rating": "1星" - }, - { - "title": "The Loney", - "price": "£23.40", - "availability": "In stock", - "rating": "1星" - }, - { - "title": "The Literature Book (Big Ideas Simply Explained)", - "price": "£17.43", - "availability": "In stock", - "rating": "3星" - }, - { - "title": "The Last Mile (Amos Decker #2)", - "price": "£54.21", - "availability": "In stock", - "rating": "2星" - }, - { - "title": "The Immortal Life of Henrietta Lacks", - "price": "£40.67", - "availability": "In stock", - "rating": "2星" - }, - { - "title": "The Hidden Oracle (The Trials of Apollo #1)", - "price": "£52.26", - "availability": "In stock", - "rating": "2星" - }, - { - "title": "The Help Yourself Cookbook for Kids: 60 Easy Plant-Based Recipes Kids Can Make to Stay Healthy and Save the Earth", - "price": "£28.77", - "availability": "In stock", - "rating": "3星" - }, - { - "title": "The Guilty (Will Robie #4)", - "price": "£13.82", - "availability": "In stock", - "rating": "2星" - }, - { - "title": "The First Hostage (J.B. Collins #2)", - "price": "£25.85", - "availability": "In stock", - "rating": "3星" - }, - { - "title": "The Dovekeepers", - "price": "£48.78", - "availability": "In stock", - "rating": "1星" - }, - { - "title": "The Darkest Lie", - "price": "£35.35", - "availability": "In stock", - "rating": "5星" - }, - { - "title": "The Bane Chronicles (The Bane Chronicles #1-11)", - "price": "£44.73", - "availability": "In stock", - "rating": "4星" - }, - { - "title": "The Bad-Ass Librarians of Timbuktu: And Their Race to Save the World’s Most Precious Manuscripts", - "price": "£15.77", - "availability": "In stock", - "rating": "1星" - }, - { - "title": "The 14th Colony (Cotton Malone #11)", - "price": "£39.24", - "availability": "In stock", - "rating": "1星" - }, - { - "title": "That Darkness (Gardiner and Renner #1)", - "price": "£13.92", - "availability": "In stock", - "rating": "1星" - }, - { - "title": "Tastes Like Fear (DI Marnie Rome #3)", - "price": "£10.69", - "availability": "In stock", - "rating": "1星" - }, - { - "title": "Take Me with You", - "price": "£45.21", - "availability": "In stock", - "rating": "3星" - }, - { - "title": "Swell: A Year of Waves", - "price": "£45.58", - "availability": "In stock", - "rating": "1星" - }, - { - "title": "Superman Vol. 1: Before Truth (Superman by Gene Luen Yang #1)", - "price": "£11.89", - "availability": "In stock", - "rating": "5星" - }, - { - "title": "Still Life with Bread Crumbs", - "price": "£26.41", - "availability": "In stock", - "rating": "3星" - }, - { - "title": "Steve Jobs", - "price": "£39.50", - "availability": "In stock", - "rating": "5星" - }, - { - "title": "Sorting the Beef from the Bull: The Science of Food Fraud Forensics", - "price": "£44.74", - "availability": "In stock", - "rating": "4星" - }, - { - "title": "Someone Like You (The Harrisons #2)", - "price": "£52.79", - "availability": "In stock", - "rating": "5星" - }, - { - "title": "So Cute It Hurts!!, Vol. 6 (So Cute It Hurts!! #6)", - "price": "£35.43", - "availability": "In stock", - "rating": "4星" - }, - { - "title": "Shtum", - "price": "£55.84", - "availability": "In stock", - "rating": "4星" - }, - { - "title": "See America: A Celebration of Our National Parks & Treasured Sites", - "price": "£48.87", - "availability": "In stock", - "rating": "3星" - }, - { - "title": "salt.", - "price": "£46.78", - "availability": "In stock", - "rating": "4星" - }, - { - "title": "Robin War", - "price": "£47.82", - "availability": "In stock", - "rating": "3星" - }, - { - "title": "Red Hood/Arsenal, Vol. 1: Open for Business (Red Hood/Arsenal #1)", - "price": "£25.48", - "availability": "In stock", - "rating": "2星" - }, - { - "title": "Rain Fish", - "price": "£23.57", - "availability": "In stock", - "rating": "3星" - }, - { - "title": "Quarter Life Poetry: Poems for the Young, Broke and Hangry", - "price": "£50.89", - "availability": "In stock", - "rating": "5星" - }, - { - "title": "Pet Sematary", - "price": "£10.56", - "availability": "In stock", - "rating": "3星" - }, - { - "title": "Overload: How to Unplug, Unwind, and Unleash Yourself from the Pressure of Stress", - "price": "£52.15", - "availability": "In stock", - "rating": "3星" - }, - { - "title": "Once Was a Time", - "price": "£18.28", - "availability": "In stock", - "rating": "2星" - }, - { - "title": "Old School (Diary of a Wimpy Kid #10)", - "price": "£11.83", - "availability": "In stock", - "rating": "5星" - }, - { - "title": "No Dream Is Too High: Life Lessons From a Man Who Walked on the Moon", - "price": "£21.95", - "availability": "In stock", - "rating": "2星" - }, - { - "title": "Naruto (3-in-1 Edition), Vol. 14: Includes Vols. 40, 41 & 42 (Naruto: Omnibus #14)", - "price": "£38.39", - "availability": "In stock", - "rating": "2星" - }, - { - "title": "My Name Is Lucy Barton", - "price": "£41.56", - "availability": "In stock", - "rating": "1星" - }, - { - "title": "My Mrs. Brown", - "price": "£24.48", - "availability": "In stock", - "rating": "3星" - }, - { - "title": "My Kind of Crazy", - "price": "£40.36", - "availability": "In stock", - "rating": "1星" - }, - { - "title": "Mr. Mercedes (Bill Hodges Trilogy #1)", - "price": "£28.90", - "availability": "In stock", - "rating": "1星" - }, - { - "title": "More Than Music (Chasing the Dream #1)", - "price": "£37.61", - "availability": "In stock", - "rating": "2星" - }, - { - "title": "Made to Stick: Why Some Ideas Survive and Others Die", - "price": "£38.85", - "availability": "In stock", - "rating": "5星" - }, - { - "title": "Luis Paints the World", - "price": "£53.95", - "availability": "In stock", - "rating": "3星" - }, - { - "title": "Luckiest Girl Alive", - "price": "£49.83", - "availability": "In stock", - "rating": "3星" - }, - { - "title": "Lowriders to the Center of the Earth (Lowriders in Space #2)", - "price": "£51.51", - "availability": "In stock", - "rating": "2星" - }, - { - "title": "Love Is a Mix Tape (Music #1)", - "price": "£18.03", - "availability": "In stock", - "rating": "1星" - }, - { - "title": "Looking for Lovely: Collecting the Moments that Matter", - "price": "£29.14", - "availability": "In stock", - "rating": "5星" - }, - { - "title": "Living Leadership by Insight: A Good Leader Achieves, a Great Leader Builds Monuments", - "price": "£46.91", - "availability": "In stock", - "rating": "4星" - }, - { - "title": "Let It Out: A Journey Through Journaling", - "price": "£26.79", - "availability": "In stock", - "rating": "5星" - }, - { - "title": "Lady Midnight (The Dark Artifices #1)", - "price": "£16.28", - "availability": "In stock", - "rating": "5星" - }, - { - "title": "It's All Easy: Healthy, Delicious Weeknight Meals in under 30 Minutes", - "price": "£19.55", - "availability": "In stock", - "rating": "1星" - }, - { - "title": "Island of Dragons (Unwanteds #7)", - "price": "£29.65", - "availability": "In stock", - "rating": "1星" - }, - { - "title": "I Know What I'm Doing -- and Other Lies I Tell Myself: Dispatches from a Life Under Construction", - "price": "£25.98", - "availability": "In stock", - "rating": "4星" - }, - { - "title": "I Am Pilgrim (Pilgrim #1)", - "price": "£10.60", - "availability": "In stock", - "rating": "4星" - }, - { - "title": "Hyperbole and a Half: Unfortunate Situations, Flawed Coping Mechanisms, Mayhem, and Other Things That Happened", - "price": "£14.75", - "availability": "In stock", - "rating": "5星" - }, - { - "title": "Hush, Hush (Hush, Hush #1)", - "price": "£47.02", - "availability": "In stock", - "rating": "3星" - }, - { - "title": "Hold Your Breath (Search and Rescue #1)", - "price": "£28.82", - "availability": "In stock", - "rating": "1星" - }, - { - "title": "Hamilton: The Revolution", - "price": "£58.79", - "availability": "In stock", - "rating": "3星" - }, - { - "title": "Greek Mythic History", - "price": "£10.23", - "availability": "In stock", - "rating": "5星" - }, - { - "title": "God: The Most Unpleasant Character in All Fiction", - "price": "£30.03", - "availability": "In stock", - "rating": "5星" - }, - { - "title": "Glory over Everything: Beyond The Kitchen House", - "price": "£45.84", - "availability": "In stock", - "rating": "3星" - }, - { - "title": "Feathers: Displays of Brilliant Plumage", - "price": "£49.05", - "availability": "In stock", - "rating": "3星" - }, - { - "title": "Far & Away: Places on the Brink of Change: Seven Continents, Twenty-Five Years", - "price": "£15.06", - "availability": "In stock", - "rating": "4星" - }, - { - "title": "Every Last Word", - "price": "£46.47", - "availability": "In stock", - "rating": "3星" - }, - { - "title": "Eligible (The Austen Project #4)", - "price": "£27.09", - "availability": "In stock", - "rating": "3星" - }, - { - "title": "El Deafo", - "price": "£57.62", - "availability": "In stock", - "rating": "5星" - }, - { - "title": "Eight Hundred Grapes", - "price": "£14.39", - "availability": "In stock", - "rating": "4星" - }, - { - "title": "Eaternity: More than 150 Deliciously Easy Vegan Recipes for a Long, Healthy, Satisfied, Joyful Life", - "price": "£51.75", - "availability": "In stock", - "rating": "5星" - }, - { - "title": "Eat Fat, Get Thin", - "price": "£54.07", - "availability": "In stock", - "rating": "2星" - }, - { - "title": "Don't Get Caught", - "price": "£55.35", - "availability": "In stock", - "rating": "1星" - }, - { - "title": "Doctor Sleep (The Shining #2)", - "price": "£40.12", - "availability": "In stock", - "rating": "2星" - }, - { - "title": "Demigods & Magicians: Percy and Annabeth Meet the Kanes (Percy Jackson & Kane Chronicles Crossover #1-3)", - "price": "£37.51", - "availability": "In stock", - "rating": "5星" - }, - { - "title": "Dear Mr. Knightley", - "price": "£11.21", - "availability": "In stock", - "rating": "5星" - }, - { - "title": "Daily Fantasy Sports", - "price": "£36.58", - "availability": "In stock", - "rating": "1星" - }, - { - "title": "Crazy Love: Overwhelmed by a Relentless God", - "price": "£47.72", - "availability": "In stock", - "rating": "2星" - }, - { - "title": "Cometh the Hour (The Clifton Chronicles #6)", - "price": "£25.01", - "availability": "In stock", - "rating": "3星" - }, - { - "title": "Code Name Verity (Code Name Verity #1)", - "price": "£22.13", - "availability": "In stock", - "rating": "4星" - }, - { - "title": "Clockwork Angel (The Infernal Devices #1)", - "price": "£44.14", - "availability": "In stock", - "rating": "1星" - }, - { - "title": "City of Glass (The Mortal Instruments #3)", - "price": "£56.02", - "availability": "In stock", - "rating": "4星" - }, - { - "title": "City of Fallen Angels (The Mortal Instruments #4)", - "price": "£11.23", - "availability": "In stock", - "rating": "4星" - }, - { - "title": "City of Bones (The Mortal Instruments #1)", - "price": "£43.28", - "availability": "In stock", - "rating": "1星" - }, - { - "title": "City of Ashes (The Mortal Instruments #2)", - "price": "£47.27", - "availability": "In stock", - "rating": "1星" - }, - { - "title": "Cell", - "price": "£20.29", - "availability": "In stock", - "rating": "4星" - }, - { - "title": "Catching Jordan (Hundred Oaks)", - "price": "£50.83", - "availability": "In stock", - "rating": "3星" - }, - { - "title": "Carry On, Warrior: Thoughts on Life Unarmed", - "price": "£31.85", - "availability": "In stock", - "rating": "3星" - }, - { - "title": "Carrie", - "price": "£46.23", - "availability": "In stock", - "rating": "2星" - }, - { - "title": "Buying In: The Secret Dialogue Between What We Buy and Who We Are", - "price": "£37.80", - "availability": "In stock", - "rating": "4星" - }, - { - "title": "Brain on Fire: My Month of Madness", - "price": "£49.32", - "availability": "In stock", - "rating": "5星" - }, - { - "title": "Batman: Europa", - "price": "£32.01", - "availability": "In stock", - "rating": "2星" - }, - { - "title": "Barefoot Contessa Back to Basics", - "price": "£28.01", - "availability": "In stock", - "rating": "1星" - }, - { - "title": "Barefoot Contessa at Home: Everyday Recipes You'll Make Over and Over Again", - "price": "£50.62", - "availability": "In stock", - "rating": "5星" - }, - { - "title": "Balloon Animals", - "price": "£17.03", - "availability": "In stock", - "rating": "3星" - }, - { - "title": "Art Ops Vol. 1", - "price": "£48.80", - "availability": "In stock", - "rating": "3星" - }, - { - "title": "Aristotle and Dante Discover the Secrets of the Universe (Aristotle and Dante Discover the Secrets of the Universe #1)", - "price": "£58.14", - "availability": "In stock", - "rating": "4星" - }, - { - "title": "Angels Walking (Angels Walking #1)", - "price": "£34.20", - "availability": "In stock", - "rating": "2星" - }, - { - "title": "Angels & Demons (Robert Langdon #1)", - "price": "£51.48", - "availability": "In stock", - "rating": "3星" - }, - { - "title": "All the Light We Cannot See", - "price": "£29.87", - "availability": "In stock", - "rating": "5星" - }, - { - "title": "Adulthood Is a Myth: A \"Sarah's Scribbles\" Collection", - "price": "£10.90", - "availability": "In stock", - "rating": "2星" - }, - { - "title": "Abstract City", - "price": "£56.37", - "availability": "In stock", - "rating": "5星" - }, - { - "title": "A Time of Torment (Charlie Parker #14)", - "price": "£48.35", - "availability": "In stock", - "rating": "5星" - }, - { - "title": "A Study in Scarlet (Sherlock Holmes #1)", - "price": "£16.73", - "availability": "In stock", - "rating": "2星" - }, - { - "title": "A Series of Catastrophes and Miracles: A True Story of Love, Science, and Cancer", - "price": "£56.48", - "availability": "In stock", - "rating": "2星" - }, - { - "title": "A People's History of the United States", - "price": "£40.79", - "availability": "In stock", - "rating": "2星" - }, - { - "title": "A Man Called Ove", - "price": "£39.72", - "availability": "In stock", - "rating": "1星" - }, - { - "title": "A Distant Mirror: The Calamitous 14th Century", - "price": "£14.58", - "availability": "In stock", - "rating": "3星" - }, - { - "title": "A Brush of Wings (Angels Walking #3)", - "price": "£55.51", - "availability": "In stock", - "rating": "1星" - }, - { - "title": "1491: New Revelations of the Americas Before Columbus", - "price": "£21.80", - "availability": "In stock", - "rating": "3星" - }, - { - "title": "The Three Searches, Meaning, and the Story", - "price": "£13.33", - "availability": "In stock", - "rating": "3星" - }, - { - "title": "Searching for Meaning in Gailana", - "price": "£38.73", - "availability": "In stock", - "rating": "1星" - }, - { - "title": "Rook", - "price": "£37.86", - "availability": "In stock", - "rating": "4星" - }, - { - "title": "My Kitchen Year: 136 Recipes That Saved My Life", - "price": "£11.53", - "availability": "In stock", - "rating": "2星" - }, - { - "title": "13 Hours: The Inside Account of What Really Happened In Benghazi", - "price": "£27.06", - "availability": "In stock", - "rating": "1星" - }, - { - "title": "Will You Won't You Want Me?", - "price": "£13.86", - "availability": "In stock", - "rating": "3星" - }, - { - "title": "Tipping Point for Planet Earth: How Close Are We to the Edge?", - "price": "£37.55", - "availability": "In stock", - "rating": "1星" - }, - { - "title": "The Star-Touched Queen", - "price": "£32.30", - "availability": "In stock", - "rating": "5星" - }, - { - "title": "The Silent Sister (Riley MacPherson #1)", - "price": "£46.29", - "availability": "In stock", - "rating": "5星" - }, - { - "title": "The Midnight Watch: A Novel of the Titanic and the Californian", - "price": "£26.20", - "availability": "In stock", - "rating": "1星" - }, - { - "title": "The Lonely City: Adventures in the Art of Being Alone", - "price": "£33.26", - "availability": "In stock", - "rating": "2星" - }, - { - "title": "The Gray Rhino: How to Recognize and Act on the Obvious Dangers We Ignore", - "price": "£59.15", - "availability": "In stock", - "rating": "4星" - }, - { - "title": "The Golden Condom: And Other Essays on Love Lost and Found", - "price": "£39.43", - "availability": "In stock", - "rating": "1星" - }, - { - "title": "The Epidemic (The Program 0.6)", - "price": "£14.44", - "availability": "In stock", - "rating": "5星" - }, - { - "title": "The Dinner Party", - "price": "£56.54", - "availability": "In stock", - "rating": "2星" - }, - { - "title": "The Diary of a Young Girl", - "price": "£59.90", - "availability": "In stock", - "rating": "3星" - }, - { - "title": "The Children", - "price": "£11.88", - "availability": "In stock", - "rating": "3星" - }, - { - "title": "Stars Above (The Lunar Chronicles #4.5)", - "price": "£48.05", - "availability": "In stock", - "rating": "2星" - }, - { - "title": "Snatched: How A Drug Queen Went Undercover for the DEA and Was Kidnapped By Colombian Guerillas", - "price": "£21.21", - "availability": "In stock", - "rating": "3星" - }, - { - "title": "Raspberry Pi Electronics Projects for the Evil Genius", - "price": "£49.67", - "availability": "In stock", - "rating": "4星" - }, - { - "title": "Quench Your Own Thirst: Business Lessons Learned Over a Beer or Two", - "price": "£43.14", - "availability": "In stock", - "rating": "1星" - }, - { - "title": "Psycho: Sanitarium (Psycho #1.5)", - "price": "£36.97", - "availability": "In stock", - "rating": "5星" - }, - { - "title": "Poisonous (Max Revere Novels #3)", - "price": "£26.80", - "availability": "In stock", - "rating": "3星" - }, - { - "title": "One with You (Crossfire #5)", - "price": "£15.71", - "availability": "In stock", - "rating": "4星" - }, - { - "title": "No Love Allowed (Dodge Cove #1)", - "price": "£54.65", - "availability": "In stock", - "rating": "4星" - }, - { - "title": "Murder at the 42nd Street Library (Raymond Ambler #1)", - "price": "£54.36", - "availability": "In stock", - "rating": "4星" - }, - { - "title": "Most Wanted", - "price": "£35.28", - "availability": "In stock", - "rating": "3星" - }, - { - "title": "Love, Lies and Spies", - "price": "£20.55", - "availability": "In stock", - "rating": "2星" - }, - { - "title": "How to Speak Golf: An Illustrated Guide to Links Lingo", - "price": "£58.32", - "availability": "In stock", - "rating": "5星" - }, - { - "title": "Hide Away (Eve Duncan #20)", - "price": "£11.84", - "availability": "In stock", - "rating": "1星" - }, - { - "title": "Furiously Happy: A Funny Book About Horrible Things", - "price": "£41.46", - "availability": "In stock", - "rating": "4星" - }, - { - "title": "Everyday Italian: 125 Simple and Delicious Recipes", - "price": "£20.10", - "availability": "In stock", - "rating": "5星" - }, - { - "title": "Equal Is Unfair: America's Misguided Fight Against Income Inequality", - "price": "£56.86", - "availability": "In stock", - "rating": "1星" - }, - { - "title": "Eleanor & Park", - "price": "£56.51", - "availability": "In stock", - "rating": "5星" - }, - { - "title": "Dirty (Dive Bar #1)", - "price": "£40.83", - "availability": "In stock", - "rating": "4星" - }, - { - "title": "Can You Keep a Secret? (Fear Street Relaunch #4)", - "price": "£48.64", - "availability": "In stock", - "rating": "1星" - }, - { - "title": "Boar Island (Anna Pigeon #19)", - "price": "£59.48", - "availability": "In stock", - "rating": "3星" - }, - { - "title": "A Paris Apartment", - "price": "£39.01", - "availability": "In stock", - "rating": "4星" - }, - { - "title": "A la Mode: 120 Recipes in 60 Pairings: Pies, Tarts, Cakes, Crisps, and More Topped with Ice Cream, Gelato, Frozen Custard, and More", - "price": "£38.77", - "availability": "In stock", - "rating": "1星" - }, - { - "title": "Troublemaker: Surviving Hollywood and Scientology", - "price": "£48.39", - "availability": "In stock", - "rating": "2星" - }, - { - "title": "The Widow", - "price": "£27.26", - "availability": "In stock", - "rating": "2星" - }, - { - "title": "The Sleep Revolution: Transforming Your Life, One Night at a Time", - "price": "£11.68", - "availability": "In stock", - "rating": "4星" - }, - { - "title": "The Improbability of Love", - "price": "£59.45", - "availability": "In stock", - "rating": "1星" - }, - { - "title": "The Art of Startup Fundraising", - "price": "£21.00", - "availability": "In stock", - "rating": "3星" - }, - { - "title": "Take Me Home Tonight (Rock Star Romance #3)", - "price": "£53.98", - "availability": "In stock", - "rating": "3星" - }, - { - "title": "Sleeping Giants (Themis Files #1)", - "price": "£48.74", - "availability": "In stock", - "rating": "1星" - }, - { - "title": "Setting the World on Fire: The Brief, Astonishing Life of St. Catherine of Siena", - "price": "£21.15", - "availability": "In stock", - "rating": "2星" - }, - { - "title": "Playing with Fire", - "price": "£13.71", - "availability": "In stock", - "rating": "3星" - }, - { - "title": "Off the Hook (Fishing for Trouble #1)", - "price": "£47.67", - "availability": "In stock", - "rating": "3星" - }, - { - "title": "Mothering Sunday", - "price": "£13.34", - "availability": "In stock", - "rating": "2星" - }, - { - "title": "Mother, Can You Not?", - "price": "£16.89", - "availability": "In stock", - "rating": "5星" - }, - { - "title": "M Train", - "price": "£27.18", - "availability": "In stock", - "rating": "1星" - }, - { - "title": "Lilac Girls", - "price": "£17.28", - "availability": "In stock", - "rating": "2星" - }, - { - "title": "Lies and Other Acts of Love", - "price": "£45.14", - "availability": "In stock", - "rating": "1星" - }, - { - "title": "Lab Girl", - "price": "£40.85", - "availability": "In stock", - "rating": "1星" - }, - { - "title": "Keep Me Posted", - "price": "£20.46", - "availability": "In stock", - "rating": "4星" - }, - { - "title": "It Didn't Start with You: How Inherited Family Trauma Shapes Who We Are and How to End the Cycle", - "price": "£56.27", - "availability": "In stock", - "rating": "3星" - }, - { - "title": "Grey (Fifty Shades #4)", - "price": "£48.49", - "availability": "In stock", - "rating": "4星" - }, - { - "title": "Exit, Pursued by a Bear", - "price": "£51.34", - "availability": "In stock", - "rating": "4星" - }, - { - "title": "Daredevils", - "price": "£16.34", - "availability": "In stock", - "rating": "3星" - }, - { - "title": "Cravings: Recipes for What You Want to Eat", - "price": "£20.50", - "availability": "In stock", - "rating": "3星" - }, - { - "title": "Born for This: How to Find the Work You Were Meant to Do", - "price": "£21.59", - "availability": "In stock", - "rating": "5星" - }, - { - "title": "Arena", - "price": "£21.36", - "availability": "In stock", - "rating": "4星" - }, - { - "title": "Adultery", - "price": "£20.88", - "availability": "In stock", - "rating": "5星" - }, - { - "title": "A Mother's Reckoning: Living in the Aftermath of Tragedy", - "price": "£19.53", - "availability": "In stock", - "rating": "3星" - }, - { - "title": "A Gentleman's Position (Society of Gentlemen #3)", - "price": "£14.75", - "availability": "In stock", - "rating": "5星" - }, - { - "title": "11/22/63", - "price": "£48.48", - "availability": "In stock", - "rating": "3星" - }, - { - "title": "10% Happier: How I Tamed the Voice in My Head, Reduced Stress Without Losing My Edge, and Found Self-Help That Actually Works", - "price": "£24.57", - "availability": "In stock", - "rating": "2星" - }, - { - "title": "10-Day Green Smoothie Cleanse: Lose Up to 15 Pounds in 10 Days!", - "price": "£49.71", - "availability": "In stock", - "rating": "5星" - }, - { - "title": "Without Shame", - "price": "£48.27", - "availability": "In stock", - "rating": "5星" - }, - { - "title": "Watchmen", - "price": "£58.05", - "availability": "In stock", - "rating": "4星" - }, - { - "title": "Unlimited Intuition Now", - "price": "£58.87", - "availability": "In stock", - "rating": "4星" - }, - { - "title": "Underlying Notes", - "price": "£11.82", - "availability": "In stock", - "rating": "2星" - }, - { - "title": "The Shack", - "price": "£28.03", - "availability": "In stock", - "rating": "1星" - }, - { - "title": "The New Brand You: Your New Image Makes the Sale for You", - "price": "£44.05", - "availability": "In stock", - "rating": "5星" - }, - { - "title": "The Moosewood Cookbook: Recipes from Moosewood Restaurant, Ithaca, New York", - "price": "£12.34", - "availability": "In stock", - "rating": "4星" - }, - { - "title": "The Flowers Lied", - "price": "£16.68", - "availability": "In stock", - "rating": "2星" - }, - { - "title": "The Fabric of the Cosmos: Space, Time, and the Texture of Reality", - "price": "£55.91", - "availability": "In stock", - "rating": "1星" - }, - { - "title": "The Book of Mormon", - "price": "£24.57", - "availability": "In stock", - "rating": "3星" - }, - { - "title": "The Art and Science of Low Carbohydrate Living", - "price": "£52.98", - "availability": "In stock", - "rating": "5星" - }, - { - "title": "The Alien Club", - "price": "£54.40", - "availability": "In stock", - "rating": "1星" - }, - { - "title": "Suzie Snowflake: One beautiful flake (a self-esteem story)", - "price": "£54.81", - "availability": "In stock", - "rating": "5星" - }, - { - "title": "Nap-a-Roo", - "price": "£25.08", - "availability": "In stock", - "rating": "1星" - }, - { - "title": "NaNo What Now? Finding your editing process, revising your NaNoWriMo book and building a writing career through publishing and beyond", - "price": "£10.41", - "availability": "In stock", - "rating": "4星" - }, - { - "title": "Modern Day Fables", - "price": "£47.44", - "availability": "In stock", - "rating": "2星" - }, - { - "title": "If I Gave You God's Phone Number....: Searching for Spirituality in America", - "price": "£20.91", - "availability": "In stock", - "rating": "1星" - }, - { - "title": "Fruits Basket, Vol. 9 (Fruits Basket #9)", - "price": "£33.95", - "availability": "In stock", - "rating": "4星" - }, - { - "title": "Dress Your Family in Corduroy and Denim", - "price": "£43.68", - "availability": "In stock", - "rating": "3星" - }, - { - "title": "Don't Forget Steven", - "price": "£33.23", - "availability": "In stock", - "rating": "1星" - }, - { - "title": "Chernobyl 01:23:40: The Incredible True Story of the World's Worst Nuclear Disaster", - "price": "£35.92", - "availability": "In stock", - "rating": "2星" - }, - { - "title": "Art and Fear: Observations on the Perils (and Rewards) of Artmaking", - "price": "£48.63", - "availability": "In stock", - "rating": "4星" - }, - { - "title": "A Shard of Ice (The Black Symphony Saga #1)", - "price": "£56.63", - "availability": "In stock", - "rating": "3星" - }, - { - "title": "A Hero's Curse (The Unseen Chronicles #1)", - "price": "£50.49", - "availability": "In stock", - "rating": "3星" - }, - { - "title": "23 Degrees South: A Tropical Tale of Changing Whether...", - "price": "£35.79", - "availability": "In stock", - "rating": "2星" - }, - { - "title": "Zero to One: Notes on Startups, or How to Build the Future", - "price": "£34.06", - "availability": "In stock", - "rating": "3星" - }, - { - "title": "Why Not Me?", - "price": "£17.76", - "availability": "In stock", - "rating": "1星" - }, - { - "title": "When Breath Becomes Air", - "price": "£39.36", - "availability": "In stock", - "rating": "2星" - }, - { - "title": "Vagabonding: An Uncommon Guide to the Art of Long-Term World Travel", - "price": "£36.94", - "availability": "In stock", - "rating": "2星" - }, - { - "title": "The Unlikely Pilgrimage of Harold Fry (Harold Fry #1)", - "price": "£43.62", - "availability": "In stock", - "rating": "5星" - }, - { - "title": "The New Drawing on the Right Side of the Brain", - "price": "£43.02", - "availability": "In stock", - "rating": "3星" - }, - { - "title": "The Midnight Assassin: Panic, Scandal, and the Hunt for America's First Serial Killer", - "price": "£28.45", - "availability": "In stock", - "rating": "4星" - }, - { - "title": "The Martian (The Martian #1)", - "price": "£41.39", - "availability": "In stock", - "rating": "2星" - }, - { - "title": "The High Mountains of Portugal", - "price": "£51.15", - "availability": "In stock", - "rating": "1星" - }, - { - "title": "The Grownup", - "price": "£35.88", - "availability": "In stock", - "rating": "1星" - }, - { - "title": "The E-Myth Revisited: Why Most Small Businesses Don't Work and What to Do About It", - "price": "£36.91", - "availability": "In stock", - "rating": "1星" - }, - { - "title": "South of Sunshine", - "price": "£28.93", - "availability": "In stock", - "rating": "1星" - }, - { - "title": "Smarter Faster Better: The Secrets of Being Productive in Life and Business", - "price": "£38.89", - "availability": "In stock", - "rating": "5星" - }, - { - "title": "Silence in the Dark (Logan Point #4)", - "price": "£58.33", - "availability": "In stock", - "rating": "3星" - }, - { - "title": "Shadows of the Past (Logan Point #1)", - "price": "£39.67", - "availability": "In stock", - "rating": "5星" - }, - { - "title": "Roller Girl", - "price": "£14.10", - "availability": "In stock", - "rating": "5星" - }, - { - "title": "Rising Strong", - "price": "£21.82", - "availability": "In stock", - "rating": "3星" - }, - { - "title": "Proofs of God: Classical Arguments from Tertullian to Barth", - "price": "£54.21", - "availability": "In stock", - "rating": "1星" - }, - { - "title": "Please Kill Me: The Uncensored Oral History of Punk", - "price": "£31.19", - "availability": "In stock", - "rating": "4星" - }, - { - "title": "Out of Print: City Lights Spotlight No. 14", - "price": "£53.64", - "availability": "In stock", - "rating": "5星" - }, - { - "title": "My Life Next Door (My Life Next Door )", - "price": "£36.39", - "availability": "In stock", - "rating": "5星" - }, - { - "title": "Miller's Valley", - "price": "£58.54", - "availability": "In stock", - "rating": "2星" - }, - { - "title": "Man's Search for Meaning", - "price": "£29.48", - "availability": "In stock", - "rating": "3星" - }, - { - "title": "Love That Boy: What Two Presidents, Eight Road Trips, and My Son Taught Me About a Parent's Expectations", - "price": "£25.06", - "availability": "In stock", - "rating": "2星" - }, - { - "title": "Living Forward: A Proven Plan to Stop Drifting and Get the Life You Want", - "price": "£12.55", - "availability": "In stock", - "rating": "3星" - }, - { - "title": "Les Fleurs du Mal", - "price": "£29.04", - "availability": "In stock", - "rating": "5星" - }, - { - "title": "Left Behind (Left Behind #1)", - "price": "£40.72", - "availability": "In stock", - "rating": "2星" - }, - { - "title": "Kill 'Em and Leave: Searching for James Brown and the American Soul", - "price": "£45.05", - "availability": "In stock", - "rating": "5星" - }, - { - "title": "Kierkegaard: A Christian Missionary to Christians", - "price": "£47.13", - "availability": "In stock", - "rating": "1星" - }, - { - "title": "John Vassos: Industrial Design for Modern Life", - "price": "£20.22", - "availability": "In stock", - "rating": "4星" - }, - { - "title": "I'll Give You the Sun", - "price": "£56.48", - "availability": "In stock", - "rating": "1星" - }, - { - "title": "I Will Find You", - "price": "£44.21", - "availability": "In stock", - "rating": "1星" - }, - { - "title": "Hystopia: A Novel", - "price": "£21.96", - "availability": "In stock", - "rating": "4星" - }, - { - "title": "Howl and Other Poems", - "price": "£40.45", - "availability": "In stock", - "rating": "2星" - }, - { - "title": "History of Beauty", - "price": "£10.29", - "availability": "In stock", - "rating": "4星" - }, - { - "title": "Heaven is for Real: A Little Boy's Astounding Story of His Trip to Heaven and Back", - "price": "£52.86", - "availability": "In stock", - "rating": "2星" - }, - { - "title": "Future Shock (Future Shock #1)", - "price": "£55.65", - "availability": "In stock", - "rating": "5星" - }, - { - "title": "Ender's Game (The Ender Quintet #1)", - "price": "£43.64", - "availability": "In stock", - "rating": "1星" - }, - { - "title": "Diary of a Citizen Scientist: Chasing Tiger Beetles and Other New Ways of Engaging the World", - "price": "£28.41", - "availability": "In stock", - "rating": "1星" - }, - { - "title": "Death by Leisure: A Cautionary Tale", - "price": "£37.51", - "availability": "In stock", - "rating": "4星" - }, - { - "title": "Brilliant Beacons: A History of the American Lighthouse", - "price": "£11.45", - "availability": "In stock", - "rating": "3星" - }, - { - "title": "Brazen: The Courage to Find the You That's Been Hiding", - "price": "£19.22", - "availability": "In stock", - "rating": "2星" - }, - { - "title": "Between the World and Me", - "price": "£56.91", - "availability": "In stock", - "rating": "4星" - }, - { - "title": "Being Mortal: Medicine and What Matters in the End", - "price": "£55.06", - "availability": "In stock", - "rating": "4星" - }, - { - "title": "A Murder Over a Girl: Justice, Gender, Junior High", - "price": "£13.20", - "availability": "In stock", - "rating": "3星" - }, - { - "title": "32 Yolks", - "price": "£53.63", - "availability": "In stock", - "rating": "2星" - }, - { - "title": "\"Most Blessed of the Patriarchs\": Thomas Jefferson and the Empire of the Imagination", - "price": "£44.48", - "availability": "In stock", - "rating": "5星" - }, - { - "title": "You Are a Badass: How to Stop Doubting Your Greatness and Start Living an Awesome Life", - "price": "£12.08", - "availability": "In stock", - "rating": "3星" - }, - { - "title": "Wildlife of New York: A Five-Borough Coloring Book", - "price": "£22.14", - "availability": "In stock", - "rating": "2星" - }, - { - "title": "What Happened on Beale Street (Secrets of the South Mysteries #2)", - "price": "£25.37", - "availability": "In stock", - "rating": "5星" - }, - { - "title": "Unreasonable Hope: Finding Faith in the God Who Brings Purpose to Your Pain", - "price": "£46.33", - "availability": "In stock", - "rating": "2星" - }, - { - "title": "Under the Tuscan Sun", - "price": "£37.33", - "availability": "In stock", - "rating": "3星" - }, - { - "title": "Toddlers Are A**holes: It's Not Your Fault", - "price": "£25.55", - "availability": "In stock", - "rating": "1星" - }, - { - "title": "The Year of Living Biblically: One Man's Humble Quest to Follow the Bible as Literally as Possible", - "price": "£34.72", - "availability": "In stock", - "rating": "1星" - }, - { - "title": "The Whale", - "price": "£35.96", - "availability": "In stock", - "rating": "4星" - }, - { - "title": "The Story of Art", - "price": "£41.14", - "availability": "In stock", - "rating": "4星" - }, - { - "title": "The Origin of Species", - "price": "£10.01", - "availability": "In stock", - "rating": "4星" - }, - { - "title": "The Great Gatsby", - "price": "£36.05", - "availability": "In stock", - "rating": "4星" - }, - { - "title": "The Good Girl", - "price": "£49.03", - "availability": "In stock", - "rating": "3星" - }, - { - "title": "The Glass Castle", - "price": "£16.24", - "availability": "In stock", - "rating": "1星" - }, - { - "title": "The Faith of Christopher Hitchens: The Restless Soul of the World's Most Notorious Atheist", - "price": "£39.55", - "availability": "In stock", - "rating": "1星" - }, - { - "title": "The Drowning Girls", - "price": "£35.67", - "availability": "In stock", - "rating": "3星" - }, - { - "title": "The Constant Princess (The Tudor Court #1)", - "price": "£16.62", - "availability": "In stock", - "rating": "3星" - }, - { - "title": "The Bourne Identity (Jason Bourne #1)", - "price": "£42.78", - "availability": "In stock", - "rating": "4星" - }, - { - "title": "The Bachelor Girl's Guide to Murder (Herringford and Watts Mysteries #1)", - "price": "£52.30", - "availability": "In stock", - "rating": "5星" - }, - { - "title": "The Art Book", - "price": "£32.34", - "availability": "In stock", - "rating": "2星" - }, - { - "title": "The 7 Habits of Highly Effective People: Powerful Lessons in Personal Change", - "price": "£33.17", - "availability": "In stock", - "rating": "4星" - }, - { - "title": "Team of Rivals: The Political Genius of Abraham Lincoln", - "price": "£20.12", - "availability": "In stock", - "rating": "5星" - }, - { - "title": "Steal Like an Artist: 10 Things Nobody Told You About Being Creative", - "price": "£20.90", - "availability": "In stock", - "rating": "2星" - }, - { - "title": "Sit, Stay, Love", - "price": "£20.90", - "availability": "In stock", - "rating": "3星" - }, - { - "title": "Sister Dear", - "price": "£40.20", - "availability": "In stock", - "rating": "4星" - }, - { - "title": "Shrunken Treasures: Literary Classics, Short, Sweet, and Silly", - "price": "£52.87", - "availability": "In stock", - "rating": "3星" - }, - { - "title": "Rich Dad, Poor Dad", - "price": "£51.74", - "availability": "In stock", - "rating": "1星" - }, - { - "title": "Raymie Nightingale", - "price": "£34.41", - "availability": "In stock", - "rating": "2星" - }, - { - "title": "Playing from the Heart", - "price": "£32.38", - "availability": "In stock", - "rating": "1星" - }, - { - "title": "Nightstruck: A Novel", - "price": "£50.35", - "availability": "In stock", - "rating": "4星" - }, - { - "title": "Naturally Lean: 125 Nourishing Gluten-Free, Plant-Based Recipes--All Under 300 Calories", - "price": "£11.38", - "availability": "In stock", - "rating": "5星" - }, - { - "title": "Meternity", - "price": "£43.58", - "availability": "In stock", - "rating": "3星" - }, - { - "title": "Memoirs of a Geisha", - "price": "£49.67", - "availability": "In stock", - "rating": "3星" - }, - { - "title": "Like Never Before (Walker Family #2)", - "price": "£28.77", - "availability": "In stock", - "rating": "2星" - }, - { - "title": "Life of Pi", - "price": "£13.22", - "availability": "In stock", - "rating": "4星" - }, - { - "title": "Leave This Song Behind: Teen Poetry at Its Best", - "price": "£51.17", - "availability": "In stock", - "rating": "5星" - }, - { - "title": "King's Folly (The Kinsman Chronicles #1)", - "price": "£39.61", - "availability": "In stock", - "rating": "5星" - }, - { - "title": "John Adams", - "price": "£57.43", - "availability": "In stock", - "rating": "4星" - }, - { - "title": "How to Cook Everything Vegetarian: Simple Meatless Recipes for Great Food (How to Cook Everything)", - "price": "£46.01", - "availability": "In stock", - "rating": "4星" - }, - { - "title": "How to Be a Domestic Goddess: Baking and the Art of Comfort Cooking", - "price": "£28.25", - "availability": "In stock", - "rating": "2星" - }, - { - "title": "Good in Bed (Cannie Shapiro #1)", - "price": "£37.05", - "availability": "In stock", - "rating": "5星" - }, - { - "title": "Fruits Basket, Vol. 7 (Fruits Basket #7)", - "price": "£19.57", - "availability": "In stock", - "rating": "1星" - }, - { - "title": "For the Love: Fighting for Grace in a World of Impossible Standards", - "price": "£45.13", - "availability": "In stock", - "rating": "3星" - }, - { - "title": "Finding God in the Ruins: How God Redeems Pain", - "price": "£46.64", - "availability": "In stock", - "rating": "2星" - }, - { - "title": "Every Heart a Doorway (Every Heart A Doorway #1)", - "price": "£12.16", - "availability": "In stock", - "rating": "5星" - }, - { - "title": "Delivering the Truth (Quaker Midwife Mystery #1)", - "price": "£20.89", - "availability": "In stock", - "rating": "4星" - }, - { - "title": "Counted With the Stars (Out from Egypt #1)", - "price": "£17.97", - "availability": "In stock", - "rating": "5星" - }, - { - "title": "Chronicles, Vol. 1", - "price": "£52.60", - "availability": "In stock", - "rating": "2星" - }, - { - "title": "Blue Like Jazz: Nonreligious Thoughts on Christian Spirituality", - "price": "£25.77", - "availability": "In stock", - "rating": "1星" - }, - { - "title": "Benjamin Franklin: An American Life", - "price": "£48.19", - "availability": "In stock", - "rating": "3星" - }, - { - "title": "At The Existentialist Café: Freedom, Being, and apricot cocktails with: Jean-Paul Sartre, Simone de Beauvoir, Albert Camus, Martin Heidegger, Edmund Husserl, Karl Jaspers, Maurice Merleau-Ponty and others", - "price": "£29.93", - "availability": "In stock", - "rating": "5星" - }, - { - "title": "A Summer In Europe", - "price": "£44.34", - "availability": "In stock", - "rating": "2星" - }, - { - "title": "A Short History of Nearly Everything", - "price": "£52.40", - "availability": "In stock", - "rating": "5星" - }, - { - "title": "A Gathering of Shadows (Shades of Magic #2)", - "price": "£44.81", - "availability": "In stock", - "rating": "4星" - }, - { - "title": "The Sound Of Love", - "price": "£57.84", - "availability": "In stock", - "rating": "5星" - }, - { - "title": "The Rise and Fall of the Third Reich: A History of Nazi Germany", - "price": "£39.67", - "availability": "In stock", - "rating": "2星" - }, - { - "title": "The Perks of Being a Wallflower", - "price": "£55.02", - "availability": "In stock", - "rating": "3星" - }, - { - "title": "The Mysterious Affair at Styles (Hercule Poirot #1)", - "price": "£24.80", - "availability": "In stock", - "rating": "4星" - }, - { - "title": "The Man Who Mistook His Wife for a Hat and Other Clinical Tales", - "price": "£59.45", - "availability": "In stock", - "rating": "4星" - }, - { - "title": "The Makings of a Fatherless Child", - "price": "£31.58", - "availability": "In stock", - "rating": "2星" - }, - { - "title": "The Joy of Cooking", - "price": "£43.27", - "availability": "In stock", - "rating": "4星" - }, - { - "title": "The Invention of Wings", - "price": "£37.34", - "availability": "In stock", - "rating": "1星" - }, - { - "title": "The Hobbit (Middle-Earth Universe)", - "price": "£17.80", - "availability": "In stock", - "rating": "5星" - }, - { - "title": "The Great Railway Bazaar", - "price": "£30.54", - "availability": "In stock", - "rating": "1星" - }, - { - "title": "The Golden Compass (His Dark Materials #1)", - "price": "£18.77", - "availability": "In stock", - "rating": "1星" - }, - { - "title": "The God Delusion", - "price": "£46.85", - "availability": "In stock", - "rating": "3星" - }, - { - "title": "The Girl You Left Behind (The Girl You Left Behind #1)", - "price": "£15.79", - "availability": "In stock", - "rating": "1星" - }, - { - "title": "The Fellowship of the Ring (The Lord of the Rings #1)", - "price": "£10.27", - "availability": "In stock", - "rating": "2星" - }, - { - "title": "The Collected Poems of W.B. Yeats (The Collected Works of W.B. Yeats #1)", - "price": "£15.42", - "availability": "In stock", - "rating": "5星" - }, - { - "title": "The Barefoot Contessa Cookbook", - "price": "£59.92", - "availability": "In stock", - "rating": "5星" - }, - { - "title": "Tell the Wolves I'm Home", - "price": "£50.96", - "availability": "In stock", - "rating": "2星" - }, - { - "title": "Ship Leaves Harbor: Essays on Travel by a Recovering Journeyman", - "price": "£30.60", - "availability": "In stock", - "rating": "3星" - }, - { - "title": "Pride and Prejudice", - "price": "£19.27", - "availability": "In stock", - "rating": "4星" - }, - { - "title": "Musicophilia: Tales of Music and the Brain", - "price": "£46.58", - "availability": "In stock", - "rating": "1星" - }, - { - "title": "Mere Christianity", - "price": "£48.51", - "availability": "In stock", - "rating": "3星" - }, - { - "title": "Me Before You (Me Before You #1)", - "price": "£19.02", - "availability": "In stock", - "rating": "1星" - }, - { - "title": "In the Woods (Dublin Murder Squad #1)", - "price": "£38.38", - "availability": "In stock", - "rating": "2星" - }, - { - "title": "In Cold Blood", - "price": "£49.98", - "availability": "In stock", - "rating": "4星" - }, - { - "title": "How to Stop Worrying and Start Living", - "price": "£46.49", - "availability": "In stock", - "rating": "5星" - }, - { - "title": "Give It Back", - "price": "£18.32", - "availability": "In stock", - "rating": "2星" - }, - { - "title": "Girl, Interrupted", - "price": "£42.14", - "availability": "In stock", - "rating": "3星" - }, - { - "title": "Fun Home: A Family Tragicomic", - "price": "£56.59", - "availability": "In stock", - "rating": "4星" - }, - { - "title": "Fruits Basket, Vol. 6 (Fruits Basket #6)", - "price": "£20.96", - "availability": "In stock", - "rating": "4星" - }, - { - "title": "Deception Point", - "price": "£40.32", - "availability": "In stock", - "rating": "4星" - }, - { - "title": "Death Note, Vol. 6: Give-and-Take (Death Note #6)", - "price": "£36.39", - "availability": "In stock", - "rating": "3星" - }, - { - "title": "Catherine the Great: Portrait of a Woman", - "price": "£58.55", - "availability": "In stock", - "rating": "4星" - }, - { - "title": "Better Homes and Gardens New Cook Book", - "price": "£39.61", - "availability": "In stock", - "rating": "3星" - }, - { - "title": "An Unquiet Mind: A Memoir of Moods and Madness", - "price": "£21.30", - "availability": "In stock", - "rating": "3星" - }, - { - "title": "A Year in Provence (Provence #1)", - "price": "£56.88", - "availability": "In stock", - "rating": "4星" - }, - { - "title": "World Without End (The Pillars of the Earth #2)", - "price": "£32.97", - "availability": "In stock", - "rating": "4星" - }, - { - "title": "Will Grayson, Will Grayson (Will Grayson, Will Grayson)", - "price": "£47.31", - "availability": "In stock", - "rating": "4星" - }, - { - "title": "Why Save the Bankers?: And Other Essays on Our Economic and Political Crisis", - "price": "£48.67", - "availability": "In stock", - "rating": "2星" - }, - { - "title": "Where She Went (If I Stay #2)", - "price": "£41.73", - "availability": "In stock", - "rating": "4星" - }, - { - "title": "What If?: Serious Scientific Answers to Absurd Hypothetical Questions", - "price": "£53.68", - "availability": "In stock", - "rating": "4星" - }, - { - "title": "Two Summers", - "price": "£14.64", - "availability": "In stock", - "rating": "1星" - }, - { - "title": "This Is Your Brain on Music: The Science of a Human Obsession", - "price": "£38.40", - "availability": "In stock", - "rating": "1星" - }, - { - "title": "The Secret Garden", - "price": "£15.08", - "availability": "In stock", - "rating": "4星" - }, - { - "title": "The Raven King (The Raven Cycle #4)", - "price": "£30.57", - "availability": "In stock", - "rating": "2星" - }, - { - "title": "The Raven Boys (The Raven Cycle #1)", - "price": "£57.74", - "availability": "In stock", - "rating": "4星" - }, - { - "title": "The Power Greens Cookbook: 140 Delicious Superfood Recipes", - "price": "£11.05", - "availability": "In stock", - "rating": "5星" - }, - { - "title": "The Metamorphosis", - "price": "£28.58", - "availability": "In stock", - "rating": "1星" - }, - { - "title": "The Mathews Men: Seven Brothers and the War Against Hitler's U-boats", - "price": "£42.91", - "availability": "In stock", - "rating": "5星" - }, - { - "title": "The Little Paris Bookshop", - "price": "£24.73", - "availability": "In stock", - "rating": "3星" - }, - { - "title": "The Hiding Place", - "price": "£55.91", - "availability": "In stock", - "rating": "4星" - }, - { - "title": "The Grand Design", - "price": "£13.76", - "availability": "In stock", - "rating": "3星" - }, - { - "title": "The Firm", - "price": "£45.56", - "availability": "In stock", - "rating": "3星" - }, - { - "title": "The Fault in Our Stars", - "price": "£47.22", - "availability": "In stock", - "rating": "1星" - }, - { - "title": "The False Prince (The Ascendance Trilogy #1)", - "price": "£56.00", - "availability": "In stock", - "rating": "5星" - }, - { - "title": "The Expatriates", - "price": "£44.58", - "availability": "In stock", - "rating": "2星" - } -] \ No newline at end of file diff --git a/project/output/news_20260530_190333.json b/project/output/news_20260530_190333.json deleted file mode 100644 index f10e931..0000000 --- a/project/output/news_20260530_190333.json +++ /dev/null @@ -1,82 +0,0 @@ -[ - { - "title": "专栏", - "publishTime": "", - "url": "http://zhuanlan.sina.com.cn/" - }, - { - "title": "导航", - "publishTime": "", - "url": "http://news.sina.com.cn/guide/" - }, - { - "title": "新浪财经", - "publishTime": "", - "url": "https://finance.sina.com.cn/mobile/comfinanceweb.shtml" - }, - { - "title": "新浪博客", - "publishTime": "", - "url": "https://blog.sina.com.cn/lm/z/app/" - }, - { - "title": "我的收藏", - "publishTime": "", - "url": "http://my.sina.com.cn/#location=fav" - }, - { - "title": "注册", - "publishTime": "", - "url": "https://login.sina.com.cn/signup/signup?entry=news" - }, - { - "title": "新闻中心", - "publishTime": "", - "url": "http://news.sina.com.cn/" - }, - { - "title": "新闻排行", - "publishTime": "", - "url": "http://news.sina.com.cn/hotnews/" - }, - { - "title": "联系我们", - "publishTime": "", - "url": "http://www.sina.com.cn/contactus.html" - }, - { - "title": "广告服务", - "publishTime": "", - "url": "http://emarketing.sina.com.cn/" - }, - { - "title": "通行证注册", - "publishTime": "", - "url": "http://login.sina.com.cn/signup/signup" - }, - { - "title": "产品答疑", - "publishTime": "", - "url": "http://help.sina.com.cn/" - }, - { - "title": "招聘信息", - "publishTime": "", - "url": "http://career.sina.com.cn/" - }, - { - "title": "网站律师", - "publishTime": "", - "url": "http://corp.sina.com.cn/lawfirm/sina.htm" - }, - { - "title": "版权所有", - "publishTime": "", - "url": "https://corp.sina.com.cn/chn/copyright.html" - }, - { - "title": "意见反馈", - "publishTime": "", - "url": "http://news.sina.com.cn/feedback/post.html" - } -] \ No newline at end of file diff --git a/project/output/university_ranking_20260530_190333.json b/project/output/university_ranking_20260530_190333.json deleted file mode 100644 index a6f98a4..0000000 --- a/project/output/university_ranking_20260530_190333.json +++ /dev/null @@ -1,212 +0,0 @@ -[ - { - "rank": 1, - "universityName": "清华大学 Tsinghua University 双一流/985/211", - "totalScore": "综合", - "province": "北京", - "category": "" - }, - { - "rank": 2, - "universityName": "北京大学 Peking University 双一流/985/211", - "totalScore": "综合", - "province": "北京", - "category": "" - }, - { - "rank": 3, - "universityName": "浙江大学 Zhejiang University 双一流/985/211", - "totalScore": "综合", - "province": "浙江", - "category": "" - }, - { - "rank": 4, - "universityName": "上海交通大学 Shanghai Jiao Tong University 双一流/985/211", - "totalScore": "综合", - "province": "上海", - "category": "" - }, - { - "rank": 5, - "universityName": "复旦大学 Fudan University 双一流/985/211", - "totalScore": "综合", - "province": "上海", - "category": "" - }, - { - "rank": 6, - "universityName": "南京大学 Nanjing University 双一流/985/211", - "totalScore": "综合", - "province": "江苏", - "category": "" - }, - { - "rank": 7, - "universityName": "中国科学技术大学 University of Science and Technology of China 双一流/985/211", - "totalScore": "理工", - "province": "安徽", - "category": "" - }, - { - "rank": 8, - "universityName": "武汉大学 Wuhan University 双一流/985/211", - "totalScore": "综合", - "province": "湖北", - "category": "" - }, - { - "rank": 9, - "universityName": "华中科技大学 Huazhong University of Science and Technology 双一流/985/211", - "totalScore": "综合", - "province": "湖北", - "category": "" - }, - { - "rank": 10, - "universityName": "西安交通大学 Xi'an Jiaotong University 双一流/985/211", - "totalScore": "综合", - "province": "陕西", - "category": "" - }, - { - "rank": 11, - "universityName": "北京航空航天大学 Beihang University 双一流/985/211", - "totalScore": "理工", - "province": "北京", - "category": "" - }, - { - "rank": 12, - "universityName": "中山大学 Sun Yat-sen University 双一流/985/211", - "totalScore": "综合", - "province": "广东", - "category": "" - }, - { - "rank": 13, - "universityName": "北京理工大学 Beijing Institute of Technology 双一流/985/211", - "totalScore": "理工", - "province": "北京", - "category": "" - }, - { - "rank": 14, - "universityName": "哈尔滨工业大学 Harbin Institute of Technology 双一流/985/211", - "totalScore": "理工", - "province": "黑龙江", - "category": "" - }, - { - "rank": 15, - "universityName": "四川大学 Sichuan University 双一流/985/211", - "totalScore": "综合", - "province": "四川", - "category": "" - }, - { - "rank": 16, - "universityName": "东南大学 Southeast University 双一流/985/211", - "totalScore": "综合", - "province": "江苏", - "category": "" - }, - { - "rank": 17, - "universityName": "中国人民大学 Renmin University of China 双一流/985/211", - "totalScore": "综合", - "province": "北京", - "category": "" - }, - { - "rank": 18, - "universityName": "同济大学 Tongji University 双一流/985/211", - "totalScore": "综合", - "province": "上海", - "category": "" - }, - { - "rank": 19, - "universityName": "北京师范大学 Beijing Normal University 双一流/985/211", - "totalScore": "师范", - "province": "北京", - "category": "" - }, - { - "rank": 20, - "universityName": "天津大学 Tianjin University 双一流/985/211", - "totalScore": "理工", - "province": "天津", - "category": "" - }, - { - "rank": 21, - "universityName": "西北工业大学 Northwestern Polytechnical University 双一流/985/211", - "totalScore": "理工", - "province": "陕西", - "category": "" - }, - { - "rank": 22, - "universityName": "山东大学 Shandong University 双一流/985/211", - "totalScore": "综合", - "province": "山东", - "category": "" - }, - { - "rank": 23, - "universityName": "南开大学 Nankai University 双一流/985/211", - "totalScore": "综合", - "province": "天津", - "category": "" - }, - { - "rank": 24, - "universityName": "厦门大学 Xiamen University 双一流/985/211", - "totalScore": "综合", - "province": "福建", - "category": "" - }, - { - "rank": 25, - "universityName": "中国农业大学 China Agricultural University 双一流/985/211", - "totalScore": "农业", - "province": "北京", - "category": "" - }, - { - "rank": 26, - "universityName": "吉林大学 Jilin University 双一流/985/211", - "totalScore": "综合", - "province": "吉林", - "category": "" - }, - { - "rank": 27, - "universityName": "中南大学 Central South University 双一流/985/211", - "totalScore": "综合", - "province": "湖南", - "category": "" - }, - { - "rank": 28, - "universityName": "大连理工大学 Dalian University of Technology 双一流/985/211", - "totalScore": "理工", - "province": "辽宁", - "category": "" - }, - { - "rank": 29, - "universityName": "湖南大学 Hunan University 双一流/985/211", - "totalScore": "综合", - "province": "湖南", - "category": "" - }, - { - "rank": 30, - "universityName": "华东师范大学 East China Normal University 双一流/985/211", - "totalScore": "师范", - "province": "上海", - "category": "" - } -] \ No newline at end of file diff --git a/project/output/weather_20260530_190333.json b/project/output/weather_20260530_190333.json deleted file mode 100644 index 537f65e..0000000 --- a/project/output/weather_20260530_190333.json +++ /dev/null @@ -1,335 +0,0 @@ -[ - { - "cityName": "上海", - "temperature": 22.7, - "humidity": 83.0, - "windSpeed": 7.8, - "weatherCode": "3", - "hourlyTimes": [ - "00:00", - "01:00", - "02:00", - "03:00", - "04:00", - "05:00", - "06:00", - "07:00", - "08:00", - "09:00", - "10:00", - "11:00", - "12:00", - "13:00", - "14:00", - "15:00", - "16:00", - "17:00", - "18:00", - "19:00", - "20:00", - "21:00", - "22:00", - "23:00" - ], - "hourlyTemperatures": [ - 19.2, - 19.0, - 18.9, - 18.3, - 18.1, - 17.8, - 18.7, - 20.9, - 23.5, - 24.9, - 26.2, - 27.0, - 27.5, - 28.1, - 28.2, - 27.4, - 26.7, - 25.0, - 23.8, - 22.7, - 22.0, - 20.6, - 19.9, - 19.4 - ], - "hourlyHumidities": [ - 83, - 84, - 85, - 87, - 89, - 92, - 90, - 79, - 55, - 43, - 38, - 34, - 33, - 31, - 30, - 32, - 35, - 45, - 54, - 63, - 67, - 73, - 76, - 78 - ], - "hourlyWindSpeeds": [ - 3.8, - 3.3, - 2.6, - 1.9, - 1.0, - 0.6, - 2.3, - 0.6, - 1.8, - 2.7, - 3.0, - 3.5, - 5.4, - 5.4, - 6.0, - 7.8, - 9.2, - 9.0, - 8.1, - 7.8, - 7.2, - 7.1, - 7.1, - 7.1 - ] - }, - { - "cityName": "广州", - "temperature": 25.9, - "humidity": 85.0, - "windSpeed": 5.3, - "weatherCode": "81", - "hourlyTimes": [ - "00:00", - "01:00", - "02:00", - "03:00", - "04:00", - "05:00", - "06:00", - "07:00", - "08:00", - "09:00", - "10:00", - "11:00", - "12:00", - "13:00", - "14:00", - "15:00", - "16:00", - "17:00", - "18:00", - "19:00", - "20:00", - "21:00", - "22:00", - "23:00" - ], - "hourlyTemperatures": [ - 27.7, - 27.2, - 26.0, - 25.5, - 25.4, - 25.0, - 25.0, - 26.0, - 28.1, - 29.3, - 30.6, - 31.9, - 33.0, - 33.8, - 33.9, - 33.6, - 34.2, - 30.5, - 29.4, - 25.9, - 26.4, - 26.5, - 26.3, - 26.2 - ], - "hourlyHumidities": [ - 85, - 87, - 82, - 84, - 85, - 90, - 92, - 87, - 76, - 70, - 63, - 57, - 54, - 53, - 53, - 54, - 51, - 69, - 72, - 95, - 97, - 96, - 98, - 98 - ], - "hourlyWindSpeeds": [ - 5.8, - 4.9, - 4.4, - 3.3, - 3.4, - 3.8, - 4.1, - 5.6, - 4.0, - 3.8, - 4.0, - 2.8, - 1.3, - 3.3, - 5.1, - 5.2, - 5.1, - 12.3, - 3.1, - 5.3, - 3.6, - 1.7, - 2.0, - 1.4 - ] - }, - { - "cityName": "北京", - "temperature": 32.3, - "humidity": 56.0, - "windSpeed": 17.1, - "weatherCode": "0", - "hourlyTimes": [ - "00:00", - "01:00", - "02:00", - "03:00", - "04:00", - "05:00", - "06:00", - "07:00", - "08:00", - "09:00", - "10:00", - "11:00", - "12:00", - "13:00", - "14:00", - "15:00", - "16:00", - "17:00", - "18:00", - "19:00", - "20:00", - "21:00", - "22:00", - "23:00" - ], - "hourlyTemperatures": [ - 22.8, - 21.9, - 21.2, - 20.1, - 19.6, - 18.8, - 19.2, - 20.7, - 23.7, - 27.0, - 29.9, - 32.5, - 34.5, - 35.8, - 36.3, - 36.6, - 36.2, - 35.7, - 34.2, - 32.3, - 30.9, - 29.9, - 29.1, - 28.6 - ], - "hourlyHumidities": [ - 56, - 60, - 63, - 69, - 71, - 75, - 74, - 67, - 57, - 45, - 37, - 28, - 21, - 18, - 20, - 21, - 26, - 26, - 30, - 33, - 35, - 36, - 35, - 34 - ], - "hourlyWindSpeeds": [ - 11.6, - 10.6, - 7.6, - 4.5, - 3.9, - 2.3, - 2.3, - 0.6, - 0.8, - 2.2, - 2.4, - 4.9, - 7.6, - 10.4, - 12.2, - 13.4, - 14.7, - 15.1, - 14.5, - 17.1, - 16.9, - 18.1, - 19.7, - 20.1 - ] - } -] \ No newline at end of file diff --git a/project/pom.xml b/project/pom.xml deleted file mode 100644 index 32ea206..0000000 --- a/project/pom.xml +++ /dev/null @@ -1,96 +0,0 @@ - - - 4.0.0 - - com.example - crawler-project - 1.0.0 - crawler-project - Java爬虫项目 - MVC + Command + Strategy模式 - - - 11 - 1.17.2 - 2.10.1 - 1.5.3 - 1.4.14 - 11 - 11 - UTF-8 - - - - - - org.jsoup - jsoup - ${jsoup.version} - - - - - com.google.code.gson - gson - ${gson.version} - - - - - org.jfree - jfreechart - ${jfreechart.version} - - - - - ch.qos.logback - logback-classic - ${logback.version} - - - - - junit - junit - 4.13.2 - test - - - - - - - org.apache.maven.plugins - maven-compiler-plugin - 3.11.0 - - ${java.version} - ${java.version} - ${project.build.sourceEncoding} - - - - org.apache.maven.plugins - maven-shade-plugin - 3.5.0 - - - package - - shade - - - - - com.example.crawler.Main - - - - - - - - - diff --git a/project/reports/book_analysis_report.txt b/project/reports/book_analysis_report.txt deleted file mode 100644 index 943ced3..0000000 --- a/project/reports/book_analysis_report.txt +++ /dev/null @@ -1,14 +0,0 @@ -========== 书籍数据分析报告 ========== -生成时间: 2026-05-30T17:47:42.026682900 -分析书籍总数: 600 - -【价格统计】 -最高价: £59.92 -最低价: £10.01 -平均价: £35.29 - -【库存统计】 -有库存: 600 本 -缺货: 0 本 - -报告生成完成 diff --git a/project/reports/news_analysis_report.txt b/project/reports/news_analysis_report.txt deleted file mode 100644 index d1794d6..0000000 --- a/project/reports/news_analysis_report.txt +++ /dev/null @@ -1,31 +0,0 @@ -========== 新闻数据分析报告 ========== -生成时间: 2026-05-30T17:47:42.145591 -分析新闻总数: 16 - -【发布时间分布】 - 00:00 - 01:00: 0 条 - 01:00 - 02:00: 0 条 - 02:00 - 03:00: 0 条 - 03:00 - 04:00: 0 条 - 04:00 - 05:00: 0 条 - 05:00 - 06:00: 0 条 - 06:00 - 07:00: 0 条 - 07:00 - 08:00: 0 条 - 08:00 - 09:00: 0 条 - 09:00 - 10:00: 0 条 - 10:00 - 11:00: 0 条 - 11:00 - 12:00: 0 条 - 12:00 - 13:00: 0 条 - 13:00 - 14:00: 0 条 - 14:00 - 15:00: 0 条 - 15:00 - 16:00: 0 条 - 16:00 - 17:00: 0 条 - 17:00 - 18:00: 16 条 - 18:00 - 19:00: 0 条 - 19:00 - 20:00: 0 条 - 20:00 - 21:00: 0 条 - 21:00 - 22:00: 0 条 - 22:00 - 23:00: 0 条 - 23:00 - 00:00: 0 条 - -报告生成完成 diff --git a/project/reports/ranking_analysis_report.txt b/project/reports/ranking_analysis_report.txt deleted file mode 100644 index 7636b13..0000000 --- a/project/reports/ranking_analysis_report.txt +++ /dev/null @@ -1,17 +0,0 @@ -========== 大学排名数据分析报告 ========== -生成时间: 2026-05-30T17:47:42.272388 -分析大学总数: 30 - -【省份排行榜 TOP 10】 - 北京: 7 所大学 - 上海: 4 所大学 - 湖北: 2 所大学 - 湖南: 2 所大学 - 天津: 2 所大学 - 陕西: 2 所大学 - 江苏: 2 所大学 - 山东: 1 所大学 - 福建: 1 所大学 - 吉林: 1 所大学 - -报告生成完成 diff --git a/project/reports/weather_analysis_report.txt b/project/reports/weather_analysis_report.txt deleted file mode 100644 index 247e060..0000000 --- a/project/reports/weather_analysis_report.txt +++ /dev/null @@ -1,29 +0,0 @@ -========== 天气数据分析报告 ========== -生成时间: 2026-05-30T17:47:42.585539200 -分析城市数量: 3 -数据来源: Open-Meteo API (CC BY 4.0) - -【多城市天气对比】 - -城市: 上海 - 当前温度: 24.0°C - 当前湿度: 83% - 风速: 8.3 km/h - 天气: 多云 - 24小时平均温度: 22.7°C - -城市: 广州 - 当前温度: 29.8°C - 当前湿度: 85% - 风速: 2.4 km/h - 天气: 小毛毛雨 - 24小时平均温度: 28.6°C - -城市: 北京 - 当前温度: 34.6°C - 当前湿度: 56% - 风速: 14.4 km/h - 天气: 晴 - 24小时平均温度: 28.2°C - -报告生成完成 diff --git a/project/src/main/java/com/example/crawler/Main.java b/project/src/main/java/com/example/crawler/Main.java deleted file mode 100644 index 09b9ddd..0000000 --- a/project/src/main/java/com/example/crawler/Main.java +++ /dev/null @@ -1,15 +0,0 @@ -package com.example.crawler; - -import com.example.crawler.controller.CrawlerController; - -/** - * 爬虫项目主入口类 - */ -public class Main { - - public static void main(String[] args) { - // 创建控制器并启动CLI界面 - CrawlerController controller = new CrawlerController(); - controller.start(); - } -} \ No newline at end of file diff --git a/project/src/main/java/com/example/crawler/chart/ChartGenerator.java b/project/src/main/java/com/example/crawler/chart/ChartGenerator.java deleted file mode 100644 index 3985f59..0000000 --- a/project/src/main/java/com/example/crawler/chart/ChartGenerator.java +++ /dev/null @@ -1,229 +0,0 @@ -package com.example.crawler.chart; - -import java.awt.Color; -import java.awt.Font; -import java.io.File; -import java.io.IOException; -import java.util.List; -import java.util.Map; -import java.util.stream.Collectors; - -import com.example.crawler.constant.CrawlerConstants; -import org.jfree.chart.ChartFactory; -import org.jfree.chart.ChartUtils; -import org.jfree.chart.JFreeChart; -import org.jfree.chart.axis.CategoryAxis; -import org.jfree.chart.axis.NumberAxis; -import org.jfree.chart.plot.CategoryPlot; -import org.jfree.chart.plot.PiePlot; -import org.jfree.chart.plot.XYPlot; -import org.jfree.chart.renderer.category.BarRenderer; -import org.jfree.chart.renderer.category.LineAndShapeRenderer; -import org.jfree.data.category.DefaultCategoryDataset; -import org.jfree.data.general.DefaultPieDataset; -import org.jfree.data.xy.XYDataset; -import org.jfree.data.xy.XYSeries; -import org.jfree.data.xy.XYSeriesCollection; - -public class ChartGenerator { - - static { - File dir = new File(CrawlerConstants.CHARTS_DIR); - if (!dir.exists()) { - dir.mkdirs(); - } - } - - public static void generatePriceHistogram(Map priceDistribution, String fileName) { - DefaultCategoryDataset dataset = createCategoryDataset(priceDistribution); - JFreeChart chart = ChartFactory.createBarChart( - "书籍价格分布", - "价格区间(£)", - "书籍数量", - dataset - ); - customizeBarChart(chart); - saveChart(chart, fileName); - } - - public static void generateRatingPieChart(Map ratingDistribution, String fileName) { - DefaultPieDataset dataset = new DefaultPieDataset<>(); - for (Map.Entry entry : ratingDistribution.entrySet()) { - dataset.setValue(entry.getKey(), entry.getValue()); - } - JFreeChart chart = ChartFactory.createPieChart( - "书籍评分分布", - dataset, - true, - true, - false - ); - customizePieChart(chart); - saveChart(chart, fileName); - } - - public static void generateNewsTimeTrend(Map hourDistribution, String fileName) { - DefaultCategoryDataset dataset = new DefaultCategoryDataset(); - for (int i = 0; i < 24; i++) { - int count = hourDistribution.getOrDefault(i, 0); - dataset.addValue(count, "新闻数量", String.format("%02d:00", i)); - } - JFreeChart chart = ChartFactory.createLineChart( - "新闻发布时间分布", - "小时", - "新闻数量", - dataset - ); - customizeLineChart(chart); - saveChart(chart, fileName); - } - - public static void generateWordFrequencyBarChart(Map wordFrequency, String fileName) { - Map top10 = wordFrequency.entrySet().stream() - .sorted(Map.Entry.comparingByValue().reversed()) - .limit(10) - .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); - - DefaultCategoryDataset dataset = new DefaultCategoryDataset(); - for (Map.Entry entry : top10.entrySet()) { - dataset.addValue(entry.getValue(), "词频", entry.getKey()); - } - JFreeChart chart = ChartFactory.createBarChart( - "新闻高频词 TOP 10", - "关键词", - "出现次数", - dataset - ); - customizeBarChart(chart); - saveChart(chart, fileName); - } - - public static void generateProvinceBarChart(Map provinceDistribution, String fileName) { - Map top10 = provinceDistribution.entrySet().stream() - .sorted(Map.Entry.comparingByValue().reversed()) - .limit(10) - .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); - - DefaultCategoryDataset dataset = createCategoryDataset(top10); - JFreeChart chart = ChartFactory.createBarChart( - "各省上榜大学数量 TOP 10", - "省份", - "大学数量", - dataset - ); - customizeBarChart(chart); - saveChart(chart, fileName); - } - - public static void generateScoreHistogram(Map scoreDistribution, String fileName) { - DefaultCategoryDataset dataset = createCategoryDataset(scoreDistribution); - JFreeChart chart = ChartFactory.createBarChart( - "大学总分分布", - "分数区间", - "大学数量", - dataset - ); - customizeBarChart(chart); - saveChart(chart, fileName); - } - - public static void generateTemperatureTrend(List times, List temperatures, String cityName, String fileName) { - XYSeries series = new XYSeries(cityName); - for (int i = 0; i < Math.min(times.size(), temperatures.size()); i++) { - series.add(i, temperatures.get(i)); - } - XYDataset dataset = new XYSeriesCollection(series); - JFreeChart chart = ChartFactory.createXYLineChart( - cityName + " 未来24小时温度变化", - "小时", - "温度(°C)", - dataset - ); - customizeXYLineChart(chart); - saveChart(chart, fileName); - } - - public static void generateMultiCityTemperatureComparison(Map> cityTemperatures, String fileName) { - XYSeriesCollection dataset = new XYSeriesCollection(); - for (Map.Entry> entry : cityTemperatures.entrySet()) { - XYSeries series = new XYSeries(entry.getKey()); - List temps = entry.getValue(); - for (int i = 0; i < Math.min(temps.size(), 24); i++) { - series.add(i, temps.get(i)); - } - dataset.addSeries(series); - } - JFreeChart chart = ChartFactory.createXYLineChart( - "多城市未来24小时温度对比", - "小时", - "温度(°C)", - dataset - ); - customizeXYLineChart(chart); - saveChart(chart, fileName); - } - - private static DefaultCategoryDataset createCategoryDataset(Map data) { - DefaultCategoryDataset dataset = new DefaultCategoryDataset(); - for (Map.Entry entry : data.entrySet()) { - dataset.addValue(entry.getValue(), "数值", entry.getKey()); - } - return dataset; - } - - private static void customizeBarChart(JFreeChart chart) { - chart.getTitle().setFont(new Font("Microsoft YaHei", Font.BOLD, 16)); - chart.getLegend().setItemFont(new Font("Microsoft YaHei", Font.PLAIN, 12)); - - CategoryPlot plot = chart.getCategoryPlot(); - CategoryAxis domainAxis = plot.getDomainAxis(); - domainAxis.setLabelFont(new Font("Microsoft YaHei", Font.PLAIN, 12)); - domainAxis.setTickLabelFont(new Font("Microsoft YaHei", Font.PLAIN, 10)); - - NumberAxis rangeAxis = (NumberAxis) plot.getRangeAxis(); - rangeAxis.setLabelFont(new Font("Microsoft YaHei", Font.PLAIN, 12)); - - BarRenderer renderer = (BarRenderer) plot.getRenderer(); - renderer.setSeriesPaint(0, new Color(79, 129, 189)); - } - - private static void customizePieChart(JFreeChart chart) { - chart.getTitle().setFont(new Font("Microsoft YaHei", Font.BOLD, 16)); - chart.getLegend().setItemFont(new Font("Microsoft YaHei", Font.PLAIN, 12)); - - PiePlot plot = (PiePlot) chart.getPlot(); - plot.setLabelFont(new Font("Microsoft YaHei", Font.PLAIN, 12)); - } - - private static void customizeLineChart(JFreeChart chart) { - chart.getTitle().setFont(new Font("Microsoft YaHei", Font.BOLD, 16)); - chart.getLegend().setItemFont(new Font("Microsoft YaHei", Font.PLAIN, 12)); - - CategoryPlot plot = chart.getCategoryPlot(); - LineAndShapeRenderer renderer = (LineAndShapeRenderer) plot.getRenderer(); - renderer.setSeriesPaint(0, new Color(79, 129, 189)); - } - - private static void customizeXYLineChart(JFreeChart chart) { - chart.getTitle().setFont(new Font("Microsoft YaHei", Font.BOLD, 16)); - chart.getLegend().setItemFont(new Font("Microsoft YaHei", Font.PLAIN, 12)); - - XYPlot plot = chart.getXYPlot(); - - NumberAxis xAxis = (NumberAxis) plot.getDomainAxis(); - xAxis.setLabelFont(new Font("Microsoft YaHei", Font.PLAIN, 12)); - - NumberAxis yAxis = (NumberAxis) plot.getRangeAxis(); - yAxis.setLabelFont(new Font("Microsoft YaHei", Font.PLAIN, 12)); - } - - private static void saveChart(JFreeChart chart, String fileName) { - try { - File file = new File(CrawlerConstants.CHARTS_DIR, fileName); - ChartUtils.saveChartAsPNG(file, chart, 800, 500); - System.out.println("图表已保存: " + file.getAbsolutePath()); - } catch (IOException e) { - System.err.println("保存图表失败: " + e.getMessage()); - } - } -} diff --git a/project/src/main/java/com/example/crawler/command/BaseCrawlCommand.java b/project/src/main/java/com/example/crawler/command/BaseCrawlCommand.java deleted file mode 100644 index 743d5b7..0000000 --- a/project/src/main/java/com/example/crawler/command/BaseCrawlCommand.java +++ /dev/null @@ -1,60 +0,0 @@ -package com.example.crawler.command; - -import com.example.crawler.constant.CrawlerConstants; -import com.example.crawler.exception.CrawlException; -import com.example.crawler.exception.NetworkException; -import com.example.crawler.repository.DataRepository; -import com.example.crawler.strategy.CrawlStrategy; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -public abstract class BaseCrawlCommand implements Command { - - protected static final Logger logger = LoggerFactory.getLogger(BaseCrawlCommand.class); - - protected DataRepository repository; - protected int maxRetries; - protected long retryDelayMs; - - public BaseCrawlCommand(DataRepository repository) { - this.repository = repository; - this.maxRetries = CrawlerConstants.MAX_RETRIES; - this.retryDelayMs = 2000; - } - - protected abstract CrawlStrategy getStrategy(); - - protected abstract void saveToRepository(Object data); - - @Override - public void execute() { - try { - Object data = crawlWithRetry(); - saveToRepository(data); - logger.info("Crawling completed and saved to repository"); - } catch (Exception e) { - logger.error("Crawling failed", e); - System.err.println("爬取失败: " + e.getMessage()); - } - } - - protected Object crawlWithRetry() throws Exception { - int attempts = 0; - while (attempts < maxRetries) { - try { - CrawlStrategy strategy = getStrategy(); - return strategy.crawl(); - } catch (NetworkException e) { - attempts++; - if (attempts < maxRetries) { - logger.warn("Network error, retrying in {}ms (attempt {}/{})", retryDelayMs, attempts, maxRetries); - Thread.sleep(retryDelayMs); - } else { - logger.error("Max retries reached, giving up"); - throw e; - } - } - } - throw new CrawlException("Max retries exceeded"); - } -} diff --git a/project/src/main/java/com/example/crawler/command/BookCommand.java b/project/src/main/java/com/example/crawler/command/BookCommand.java deleted file mode 100644 index 5d6df8a..0000000 --- a/project/src/main/java/com/example/crawler/command/BookCommand.java +++ /dev/null @@ -1,32 +0,0 @@ -package com.example.crawler.command; - -import com.example.crawler.model.Book; -import com.example.crawler.repository.DataRepository; -import com.example.crawler.strategy.BookCrawlStrategy; -import com.example.crawler.strategy.CrawlStrategy; - -import java.util.List; - -public class BookCommand extends BaseCrawlCommand { - - public BookCommand(DataRepository repository) { - super(repository); - } - - @Override - protected CrawlStrategy getStrategy() { - return new BookCrawlStrategy(); - } - - @Override - @SuppressWarnings("unchecked") - protected void saveToRepository(Object data) { - repository.saveBooks((List) data); - System.out.println("成功爬取 " + ((List) data).size() + " 本书籍信息"); - } - - @Override - public String getName() { - return "爬取书籍信息"; - } -} diff --git a/project/src/main/java/com/example/crawler/command/Command.java b/project/src/main/java/com/example/crawler/command/Command.java deleted file mode 100644 index 4c804a7..0000000 --- a/project/src/main/java/com/example/crawler/command/Command.java +++ /dev/null @@ -1,20 +0,0 @@ -package com.example.crawler.command; - -/** - * 命令接口 - * 定义命令执行的标准方法,实现Command模式 - */ -public interface Command { - - /** - * 执行命令 - */ - void execute(); - - /** - * 获取命令名称 - * - * @return 命令名称 - */ - String getName(); -} \ No newline at end of file diff --git a/project/src/main/java/com/example/crawler/command/CrawlAllCommand.java b/project/src/main/java/com/example/crawler/command/CrawlAllCommand.java deleted file mode 100644 index d54f6b8..0000000 --- a/project/src/main/java/com/example/crawler/command/CrawlAllCommand.java +++ /dev/null @@ -1,45 +0,0 @@ -package com.example.crawler.command; - -import com.example.crawler.controller.CrawlerController; -import com.example.crawler.repository.DataRepository; - -public class CrawlAllCommand implements Command { - - private final DataRepository repository; - private final CrawlerController controller; - - public CrawlAllCommand(CrawlerController controller) { - this.controller = controller; - this.repository = controller.getRepository(); - } - - @Override - public void execute() { - System.out.println("\n=== 开始爬取全部数据源 ==="); - - Command[] commands = { - new BookCommand(repository), - new NewsCommand(repository), - new CrawlRankingCommand(repository), - new WeatherCommand(repository) - }; - - for (Command command : commands) { - command.execute(); - try { - Thread.sleep(2000); - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - } - } - - new SaveCommand(controller).execute(); - - System.out.println("\n=== 全部数据爬取完成 ==="); - } - - @Override - public String getName() { - return "爬取全部数据并保存"; - } -} diff --git a/project/src/main/java/com/example/crawler/command/CrawlAndAnalyzeAllCommand.java b/project/src/main/java/com/example/crawler/command/CrawlAndAnalyzeAllCommand.java deleted file mode 100644 index 0174398..0000000 --- a/project/src/main/java/com/example/crawler/command/CrawlAndAnalyzeAllCommand.java +++ /dev/null @@ -1,104 +0,0 @@ -package com.example.crawler.command; - -import com.example.crawler.controller.CrawlerController; -import com.example.crawler.repository.DataRepository; -import com.example.crawler.service.BookAnalysisService; -import com.example.crawler.service.NewsAnalysisService; -import com.example.crawler.service.RankingAnalysisService; -import com.example.crawler.service.WeatherAnalysisService; - -public class CrawlAndAnalyzeAllCommand implements Command { - - private final DataRepository repository; - private final CrawlerController controller; - - public CrawlAndAnalyzeAllCommand(CrawlerController controller) { - this.controller = controller; - this.repository = controller.getRepository(); - } - - @Override - public void execute() { - System.out.println("\n========== 爬取全部数据并生成分析 ==========\n"); - - System.out.println("第1步:爬取书籍信息..."); - try { - BookCommand bookCommand = new BookCommand(repository); - bookCommand.execute(); - } catch (Exception e) { - System.err.println("书籍爬取失败: " + e.getMessage()); - } - - System.out.println("\n第2步:爬取新闻信息..."); - try { - NewsCommand newsCommand = new NewsCommand(repository); - newsCommand.execute(); - } catch (Exception e) { - System.err.println("新闻爬取失败: " + e.getMessage()); - } - - System.out.println("\n第3步:爬取大学排名..."); - try { - CrawlRankingCommand rankingCommand = new CrawlRankingCommand(repository); - rankingCommand.execute(); - } catch (Exception e) { - System.err.println("大学排名爬取失败: " + e.getMessage()); - } - - System.out.println("\n第4步:爬取天气数据..."); - try { - WeatherCommand weatherCommand = new WeatherCommand(repository); - weatherCommand.execute(); - } catch (Exception e) { - System.err.println("天气数据爬取失败: " + e.getMessage()); - } - - System.out.println("\n========== 数据爬取完成,开始分析 ==========\n"); - - try { - BookAnalysisService bookService = new BookAnalysisService(); - if (!repository.getBooks().isEmpty()) { - bookService.analyze(repository.getBooks()); - } - } catch (Exception e) { - System.err.println("书籍分析失败: " + e.getMessage()); - } - - try { - NewsAnalysisService newsService = new NewsAnalysisService(); - if (!repository.getNewsList().isEmpty()) { - newsService.analyze(repository.getNewsList()); - } - } catch (Exception e) { - System.err.println("新闻分析失败: " + e.getMessage()); - } - - try { - RankingAnalysisService rankingService = new RankingAnalysisService(); - if (!repository.getRankings().isEmpty()) { - rankingService.analyze(repository.getRankings()); - } - } catch (Exception e) { - System.err.println("大学排名分析失败: " + e.getMessage()); - } - - try { - WeatherAnalysisService weatherService = new WeatherAnalysisService(); - if (!repository.getWeatherList().isEmpty()) { - weatherService.analyze(repository.getWeatherList()); - } - } catch (Exception e) { - System.err.println("天气分析失败: " + e.getMessage()); - } - - System.out.println("\n========== 全部完成 =========="); - System.out.println("原始数据已保存到 output/ 目录"); - System.out.println("分析报告已保存到 reports/ 目录"); - System.out.println("图表已保存到 charts/ 目录"); - } - - @Override - public String getName() { - return "爬取并分析全部数据"; - } -} diff --git a/project/src/main/java/com/example/crawler/command/CrawlRankingCommand.java b/project/src/main/java/com/example/crawler/command/CrawlRankingCommand.java deleted file mode 100644 index f9cfc73..0000000 --- a/project/src/main/java/com/example/crawler/command/CrawlRankingCommand.java +++ /dev/null @@ -1,32 +0,0 @@ -package com.example.crawler.command; - -import com.example.crawler.model.UniversityRank; -import com.example.crawler.repository.DataRepository; -import com.example.crawler.strategy.CrawlStrategy; -import com.example.crawler.strategy.UniversityRankCrawlStrategy; - -import java.util.List; - -public class CrawlRankingCommand extends BaseCrawlCommand { - - public CrawlRankingCommand(DataRepository repository) { - super(repository); - } - - @Override - protected CrawlStrategy getStrategy() { - return new UniversityRankCrawlStrategy(); - } - - @Override - @SuppressWarnings("unchecked") - protected void saveToRepository(Object data) { - repository.saveRankings((List) data); - System.out.println("成功爬取 " + ((List) data).size() + " 条大学排名数据"); - } - - @Override - public String getName() { - return "爬取软科中国大学排名"; - } -} diff --git a/project/src/main/java/com/example/crawler/command/ExitCommand.java b/project/src/main/java/com/example/crawler/command/ExitCommand.java deleted file mode 100644 index 8085f2b..0000000 --- a/project/src/main/java/com/example/crawler/command/ExitCommand.java +++ /dev/null @@ -1,19 +0,0 @@ -package com.example.crawler.command; - -/** - * 退出命令 - * // Command模式:退出命令 - */ -public class ExitCommand implements Command { - - @Override - public void execute() { - System.out.println("\n=== 感谢使用数据爬取系统 ==="); - System.exit(0); - } - - @Override - public String getName() { - return "退出"; - } -} \ No newline at end of file diff --git a/project/src/main/java/com/example/crawler/command/GenerateAllAnalysisCommand.java b/project/src/main/java/com/example/crawler/command/GenerateAllAnalysisCommand.java deleted file mode 100644 index 0ed7164..0000000 --- a/project/src/main/java/com/example/crawler/command/GenerateAllAnalysisCommand.java +++ /dev/null @@ -1,77 +0,0 @@ -package com.example.crawler.command; - -import com.example.crawler.controller.CrawlerController; -import com.example.crawler.repository.DataRepository; -import com.example.crawler.service.BookAnalysisService; -import com.example.crawler.service.NewsAnalysisService; -import com.example.crawler.service.RankingAnalysisService; -import com.example.crawler.service.WeatherAnalysisService; - -public class GenerateAllAnalysisCommand implements Command { - - private final DataRepository repository; - private final CrawlerController controller; - - public GenerateAllAnalysisCommand(CrawlerController controller) { - this.controller = controller; - this.repository = controller.getRepository(); - } - - @Override - public void execute() { - System.out.println("\n========== 生成所有数据源分析报告 ==========\n"); - - try { - BookAnalysisService bookService = new BookAnalysisService(); - if (!repository.getBooks().isEmpty()) { - bookService.analyze(repository.getBooks()); - } else { - System.out.println("没有书籍数据,跳过书籍分析"); - } - } catch (Exception e) { - System.err.println("书籍分析失败: " + e.getMessage()); - } - - try { - NewsAnalysisService newsService = new NewsAnalysisService(); - if (!repository.getNewsList().isEmpty()) { - newsService.analyze(repository.getNewsList()); - } else { - System.out.println("没有新闻数据,跳过新闻分析"); - } - } catch (Exception e) { - System.err.println("新闻分析失败: " + e.getMessage()); - } - - try { - RankingAnalysisService rankingService = new RankingAnalysisService(); - if (!repository.getRankings().isEmpty()) { - rankingService.analyze(repository.getRankings()); - } else { - System.out.println("没有大学排名数据,跳过排名分析"); - } - } catch (Exception e) { - System.err.println("大学排名分析失败: " + e.getMessage()); - } - - try { - WeatherAnalysisService weatherService = new WeatherAnalysisService(); - if (!repository.getWeatherList().isEmpty()) { - weatherService.analyze(repository.getWeatherList()); - } else { - System.out.println("没有天气数据,跳过天气分析"); - } - } catch (Exception e) { - System.err.println("天气分析失败: " + e.getMessage()); - } - - System.out.println("\n========== 分析完成 =========="); - System.out.println("报告已保存到 reports/ 目录"); - System.out.println("图表已保存到 charts/ 目录"); - } - - @Override - public String getName() { - return "生成所有分析报告"; - } -} diff --git a/project/src/main/java/com/example/crawler/command/NewsCommand.java b/project/src/main/java/com/example/crawler/command/NewsCommand.java deleted file mode 100644 index 1250d1a..0000000 --- a/project/src/main/java/com/example/crawler/command/NewsCommand.java +++ /dev/null @@ -1,32 +0,0 @@ -package com.example.crawler.command; - -import com.example.crawler.model.News; -import com.example.crawler.repository.DataRepository; -import com.example.crawler.strategy.CrawlStrategy; -import com.example.crawler.strategy.NewsCrawlStrategy; - -import java.util.List; - -public class NewsCommand extends BaseCrawlCommand { - - public NewsCommand(DataRepository repository) { - super(repository); - } - - @Override - protected CrawlStrategy getStrategy() { - return new NewsCrawlStrategy(); - } - - @Override - @SuppressWarnings("unchecked") - protected void saveToRepository(Object data) { - repository.saveNewsList((List) data); - System.out.println("成功爬取 " + ((List) data).size() + " 条新闻"); - } - - @Override - public String getName() { - return "爬取新浪国内新闻"; - } -} diff --git a/project/src/main/java/com/example/crawler/command/SaveCommand.java b/project/src/main/java/com/example/crawler/command/SaveCommand.java deleted file mode 100644 index f6e606a..0000000 --- a/project/src/main/java/com/example/crawler/command/SaveCommand.java +++ /dev/null @@ -1,74 +0,0 @@ -package com.example.crawler.command; - -import java.time.LocalDateTime; -import java.time.format.DateTimeFormatter; -import java.util.List; - -import com.example.crawler.constant.CrawlerConstants; -import com.example.crawler.controller.CrawlerController; -import com.example.crawler.model.Book; -import com.example.crawler.model.News; -import com.example.crawler.model.UniversityRank; -import com.example.crawler.model.Weather; -import com.example.crawler.util.JsonUtil; - -public class SaveCommand implements Command { - - private final CrawlerController controller; - - public SaveCommand(CrawlerController controller) { - this.controller = controller; - } - - @Override - public void execute() { - System.out.println("\n=== 开始保存数据 ==="); - - try { - String timestamp = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMdd_HHmmss")); - - // 保存书籍数据 - List books = controller.getBooks(); - if (books != null && !books.isEmpty()) { - String bookFileName = CrawlerConstants.OUTPUT_DIR + "/books_" + timestamp + ".json"; - JsonUtil.saveListToJsonFile(books, bookFileName); - System.out.println("书籍数据已保存到: " + bookFileName); - } - - // 保存新闻数据 - List newsList = controller.getNewsList(); - if (newsList != null && !newsList.isEmpty()) { - String newsFileName = CrawlerConstants.OUTPUT_DIR + "/news_" + timestamp + ".json"; - JsonUtil.saveListToJsonFile(newsList, newsFileName); - System.out.println("新闻数据已保存到: " + newsFileName); - } - - // 保存大学排名数据 - List universityRankList = controller.getUniversityRankList(); - if (universityRankList != null && !universityRankList.isEmpty()) { - String rankingFileName = CrawlerConstants.OUTPUT_DIR + "/university_ranking_" + timestamp + ".json"; - JsonUtil.saveListToJsonFile(universityRankList, rankingFileName); - System.out.println("大学排名数据已保存到: " + rankingFileName); - } - - // 保存天气数据 - List weatherList = controller.getWeatherList(); - if (weatherList != null && !weatherList.isEmpty()) { - String weatherFileName = CrawlerConstants.OUTPUT_DIR + "/weather_" + timestamp + ".json"; - JsonUtil.saveListToJsonFile(weatherList, weatherFileName); - System.out.println("天气数据已保存到: " + weatherFileName); - } - - System.out.println("\n=== 数据保存完成 ==="); - - } catch (Exception e) { - System.err.println("保存数据失败: " + e.getMessage()); - e.printStackTrace(); - } - } - - @Override - public String getName() { - return "保存当前数据到文件"; - } -} \ No newline at end of file diff --git a/project/src/main/java/com/example/crawler/command/WeatherCommand.java b/project/src/main/java/com/example/crawler/command/WeatherCommand.java deleted file mode 100644 index 7a3f7a6..0000000 --- a/project/src/main/java/com/example/crawler/command/WeatherCommand.java +++ /dev/null @@ -1,32 +0,0 @@ -package com.example.crawler.command; - -import com.example.crawler.model.Weather; -import com.example.crawler.repository.DataRepository; -import com.example.crawler.strategy.CrawlStrategy; -import com.example.crawler.strategy.WeatherCrawlStrategy; - -import java.util.List; - -public class WeatherCommand extends BaseCrawlCommand { - - public WeatherCommand(DataRepository repository) { - super(repository); - } - - @Override - protected CrawlStrategy getStrategy() { - return new WeatherCrawlStrategy(); - } - - @Override - @SuppressWarnings("unchecked") - protected void saveToRepository(Object data) { - repository.saveWeatherList((List) data); - System.out.println("成功爬取 " + ((List) data).size() + " 个城市的天气信息"); - } - - @Override - public String getName() { - return "爬取天气数据"; - } -} diff --git a/project/src/main/java/com/example/crawler/constant/CrawlerConstants.java b/project/src/main/java/com/example/crawler/constant/CrawlerConstants.java deleted file mode 100644 index 841ce75..0000000 --- a/project/src/main/java/com/example/crawler/constant/CrawlerConstants.java +++ /dev/null @@ -1,31 +0,0 @@ -package com.example.crawler.constant; - -import java.util.HashMap; -import java.util.Map; - -public class CrawlerConstants { - - public static final String USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36"; - public static final String REFERER = "https://www.baidu.com"; - - public static final int TIMEOUT_MS = 10000; - public static final int MAX_RETRIES = 3; - public static final long DELAY_MS = 3000; - - public static final String URL_BOOKS = "https://books.toscrape.com/"; - public static final String URL_NEWS = "https://news.sina.com.cn/china/"; - public static final String URL_RANKING = "https://www.shanghairanking.cn/rankings/bcur/202310"; - public static final String URL_WEATHER_API = "https://api.open-meteo.com/v1/forecast"; - - public static final String OUTPUT_DIR = "output"; - public static final String REPORTS_DIR = "reports"; - public static final String CHARTS_DIR = "charts"; - - public static final Map CITY_COORDINATES; - static { - CITY_COORDINATES = new HashMap<>(); - CITY_COORDINATES.put("北京", new double[]{39.9042, 116.4074}); - CITY_COORDINATES.put("上海", new double[]{31.2304, 121.4737}); - CITY_COORDINATES.put("广州", new double[]{23.1291, 113.2644}); - } -} diff --git a/project/src/main/java/com/example/crawler/controller/CrawlerController.java b/project/src/main/java/com/example/crawler/controller/CrawlerController.java deleted file mode 100644 index cedc475..0000000 --- a/project/src/main/java/com/example/crawler/controller/CrawlerController.java +++ /dev/null @@ -1,90 +0,0 @@ -package com.example.crawler.controller; - -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Scanner; - -import com.example.crawler.command.BookCommand; -import com.example.crawler.command.Command; -import com.example.crawler.command.CrawlAllCommand; -import com.example.crawler.command.CrawlAndAnalyzeAllCommand; -import com.example.crawler.command.CrawlRankingCommand; -import com.example.crawler.command.ExitCommand; -import com.example.crawler.command.GenerateAllAnalysisCommand; -import com.example.crawler.command.NewsCommand; -import com.example.crawler.command.SaveCommand; -import com.example.crawler.command.WeatherCommand; -import com.example.crawler.model.Book; -import com.example.crawler.model.News; -import com.example.crawler.model.UniversityRank; -import com.example.crawler.model.Weather; -import com.example.crawler.repository.DataRepository; -import com.example.crawler.view.CrawlerView; - -public class CrawlerController { - - private final CrawlerView view; - private final Map commandMap; - private final DataRepository repository; - - public CrawlerController() { - this.view = new CrawlerView(); - this.repository = DataRepository.getInstance(); - this.commandMap = new HashMap<>(); - initCommands(); - } - - private void initCommands() { - commandMap.put(1, new BookCommand(repository)); - commandMap.put(2, new NewsCommand(repository)); - commandMap.put(3, new CrawlRankingCommand(repository)); - commandMap.put(4, new WeatherCommand(repository)); - commandMap.put(5, new CrawlAllCommand(this)); - commandMap.put(6, new SaveCommand(this)); - commandMap.put(7, new GenerateAllAnalysisCommand(this)); - commandMap.put(8, new CrawlAndAnalyzeAllCommand(this)); - commandMap.put(9, new ExitCommand()); - } - - public void start() { - Scanner scanner = new Scanner(System.in); - - while (true) { - view.showMenu(); - - int choice = view.getInput(scanner); - - Command command = commandMap.get(choice); - if (command != null) { - command.execute(); - } else { - view.showError("无效的选择,请输入1-9之间的数字"); - } - - if (choice != 9) { - view.pause(scanner); - } - } - } - - public List getBooks() { - return repository.getBooks(); - } - - public List getNewsList() { - return repository.getNewsList(); - } - - public List getUniversityRankList() { - return repository.getRankings(); - } - - public List getWeatherList() { - return repository.getWeatherList(); - } - - public DataRepository getRepository() { - return repository; - } -} diff --git a/project/src/main/java/com/example/crawler/exception/CrawlException.java b/project/src/main/java/com/example/crawler/exception/CrawlException.java deleted file mode 100644 index dad0237..0000000 --- a/project/src/main/java/com/example/crawler/exception/CrawlException.java +++ /dev/null @@ -1,16 +0,0 @@ -package com.example.crawler.exception; - -/** - * 爬虫异常基类 - * 所有爬虫相关异常都继承此类 - */ -public class CrawlException extends Exception { - - public CrawlException(String message) { - super(message); - } - - public CrawlException(String message, Throwable cause) { - super(message, cause); - } -} \ No newline at end of file diff --git a/project/src/main/java/com/example/crawler/exception/DataSaveException.java b/project/src/main/java/com/example/crawler/exception/DataSaveException.java deleted file mode 100644 index 6d26120..0000000 --- a/project/src/main/java/com/example/crawler/exception/DataSaveException.java +++ /dev/null @@ -1,16 +0,0 @@ -package com.example.crawler.exception; - -/** - * 数据保存异常 - * 用于处理文件写入失败、JSON序列化失败等数据保存相关错误 - */ -public class DataSaveException extends CrawlException { - - public DataSaveException(String message) { - super(message); - } - - public DataSaveException(String message, Throwable cause) { - super(message, cause); - } -} \ No newline at end of file diff --git a/project/src/main/java/com/example/crawler/exception/NetworkException.java b/project/src/main/java/com/example/crawler/exception/NetworkException.java deleted file mode 100644 index ecc898e..0000000 --- a/project/src/main/java/com/example/crawler/exception/NetworkException.java +++ /dev/null @@ -1,16 +0,0 @@ -package com.example.crawler.exception; - -/** - * 网络异常 - * 用于处理HTTP请求失败、连接超时等网络相关错误 - */ -public class NetworkException extends CrawlException { - - public NetworkException(String message) { - super(message); - } - - public NetworkException(String message, Throwable cause) { - super(message, cause); - } -} \ No newline at end of file diff --git a/project/src/main/java/com/example/crawler/exception/ParseException.java b/project/src/main/java/com/example/crawler/exception/ParseException.java deleted file mode 100644 index ecae303..0000000 --- a/project/src/main/java/com/example/crawler/exception/ParseException.java +++ /dev/null @@ -1,16 +0,0 @@ -package com.example.crawler.exception; - -/** - * 解析异常 - * 用于处理HTML解析失败、JSON解析失败等数据解析相关错误 - */ -public class ParseException extends CrawlException { - - public ParseException(String message) { - super(message); - } - - public ParseException(String message, Throwable cause) { - super(message, cause); - } -} \ No newline at end of file diff --git a/project/src/main/java/com/example/crawler/model/Book.java b/project/src/main/java/com/example/crawler/model/Book.java deleted file mode 100644 index c58fe68..0000000 --- a/project/src/main/java/com/example/crawler/model/Book.java +++ /dev/null @@ -1,65 +0,0 @@ -package com.example.crawler.model; - -/** - * 书籍数据模型 - * 存储toscrape.com网站的书籍信息 - */ -public class Book { - - private String title; - private String price; - private String availability; - private String rating; - - public Book() { - } - - public Book(String title, String price, String availability, String rating) { - this.title = title; - this.price = price; - this.availability = availability; - this.rating = rating; - } - - public String getTitle() { - return title; - } - - public void setTitle(String title) { - this.title = title; - } - - public String getPrice() { - return price; - } - - public void setPrice(String price) { - this.price = price; - } - - public String getAvailability() { - return availability; - } - - public void setAvailability(String availability) { - this.availability = availability; - } - - public String getRating() { - return rating; - } - - public void setRating(String rating) { - this.rating = rating; - } - - @Override - public String toString() { - return "Book{" + - "title='" + title + '\'' + - ", price='" + price + '\'' + - ", availability='" + availability + '\'' + - ", rating='" + rating + '\'' + - '}'; - } -} \ No newline at end of file diff --git a/project/src/main/java/com/example/crawler/model/News.java b/project/src/main/java/com/example/crawler/model/News.java deleted file mode 100644 index d5a5c21..0000000 --- a/project/src/main/java/com/example/crawler/model/News.java +++ /dev/null @@ -1,54 +0,0 @@ -package com.example.crawler.model; - -/** - * 新闻数据模型 - * 存储新浪新闻的国内新闻信息 - */ -public class News { - - private String title; - private String publishTime; - private String url; - - public News() { - } - - public News(String title, String publishTime, String url) { - this.title = title; - this.publishTime = publishTime; - this.url = url; - } - - public String getTitle() { - return title; - } - - public void setTitle(String title) { - this.title = title; - } - - public String getPublishTime() { - return publishTime; - } - - public void setPublishTime(String publishTime) { - this.publishTime = publishTime; - } - - public String getUrl() { - return url; - } - - public void setUrl(String url) { - this.url = url; - } - - @Override - public String toString() { - return "News{" + - "title='" + title + '\'' + - ", publishTime='" + publishTime + '\'' + - ", url='" + url + '\'' + - '}'; - } -} \ No newline at end of file diff --git a/project/src/main/java/com/example/crawler/model/UniversityRank.java b/project/src/main/java/com/example/crawler/model/UniversityRank.java deleted file mode 100644 index bde80a7..0000000 --- a/project/src/main/java/com/example/crawler/model/UniversityRank.java +++ /dev/null @@ -1,76 +0,0 @@ -package com.example.crawler.model; - -/** - * 大学排名数据模型 - * 存储软科中国大学排名信息 - */ -public class UniversityRank { - - private Integer rank; - private String universityName; - private String totalScore; - private String province; - private String category; - - public UniversityRank() { - } - - public UniversityRank(Integer rank, String universityName, String totalScore, String province, String category) { - this.rank = rank; - this.universityName = universityName; - this.totalScore = totalScore; - this.province = province; - this.category = category; - } - - public Integer getRank() { - return rank; - } - - public void setRank(Integer rank) { - this.rank = rank; - } - - public String getUniversityName() { - return universityName; - } - - public void setUniversityName(String universityName) { - this.universityName = universityName; - } - - public String getTotalScore() { - return totalScore; - } - - public void setTotalScore(String totalScore) { - this.totalScore = totalScore; - } - - public String getProvince() { - return province; - } - - public void setProvince(String province) { - this.province = province; - } - - public String getCategory() { - return category; - } - - public void setCategory(String category) { - this.category = category; - } - - @Override - public String toString() { - return "UniversityRank{" + - "rank=" + rank + - ", universityName='" + universityName + '\'' + - ", totalScore='" + totalScore + '\'' + - ", province='" + province + '\'' + - ", category='" + category + '\'' + - '}'; - } -} \ No newline at end of file diff --git a/project/src/main/java/com/example/crawler/model/Weather.java b/project/src/main/java/com/example/crawler/model/Weather.java deleted file mode 100644 index dbc6ccc..0000000 --- a/project/src/main/java/com/example/crawler/model/Weather.java +++ /dev/null @@ -1,140 +0,0 @@ -package com.example.crawler.model; - -import java.util.ArrayList; -import java.util.List; - -/** - * 天气数据模型 - * 存储 Open-Meteo API 的城市天气信息 - * 数据来源:Open-Meteo (CC BY 4.0) - */ -public class Weather { - - private String cityName; - private double temperature; - private double humidity; - private double windSpeed; - private String weatherCode; - private List hourlyTimes; - private List hourlyTemperatures; - private List hourlyHumidities; - private List hourlyWindSpeeds; - - public Weather() { - this.hourlyTimes = new ArrayList<>(); - this.hourlyTemperatures = new ArrayList<>(); - this.hourlyHumidities = new ArrayList<>(); - this.hourlyWindSpeeds = new ArrayList<>(); - } - - public Weather(String cityName, double temperature, double humidity, double windSpeed, String weatherCode) { - this.cityName = cityName; - this.temperature = temperature; - this.humidity = humidity; - this.windSpeed = windSpeed; - this.weatherCode = weatherCode; - this.hourlyTimes = new ArrayList<>(); - this.hourlyTemperatures = new ArrayList<>(); - this.hourlyHumidities = new ArrayList<>(); - this.hourlyWindSpeeds = new ArrayList<>(); - } - - public String getCityName() { - return cityName; - } - - public void setCityName(String cityName) { - this.cityName = cityName; - } - - public double getTemperature() { - return temperature; - } - - public void setTemperature(double temperature) { - this.temperature = temperature; - } - - public double getHumidity() { - return humidity; - } - - public void setHumidity(double humidity) { - this.humidity = humidity; - } - - public double getWindSpeed() { - return windSpeed; - } - - public void setWindSpeed(double windSpeed) { - this.windSpeed = windSpeed; - } - - public String getWeatherCode() { - return weatherCode; - } - - public void setWeatherCode(String weatherCode) { - this.weatherCode = weatherCode; - } - - public List getHourlyTimes() { - return hourlyTimes; - } - - public void setHourlyTimes(List hourlyTimes) { - this.hourlyTimes = hourlyTimes; - } - - public List getHourlyTemperatures() { - return hourlyTemperatures; - } - - public void setHourlyTemperatures(List hourlyTemperatures) { - this.hourlyTemperatures = hourlyTemperatures; - } - - public List getHourlyHumidities() { - return hourlyHumidities; - } - - public void setHourlyHumidities(List hourlyHumidities) { - this.hourlyHumidities = hourlyHumidities; - } - - public List getHourlyWindSpeeds() { - return hourlyWindSpeeds; - } - - public void setHourlyWindSpeeds(List hourlyWindSpeeds) { - this.hourlyWindSpeeds = hourlyWindSpeeds; - } - - public String getWeatherDescription() { - if (weatherCode == null) return "未知"; - switch (weatherCode) { - case "0": return "晴"; - case "1": case "2": case "3": return "多云"; - case "45": case "48": return "雾"; - case "51": case "53": case "55": return "小毛毛雨"; - case "61": case "63": case "65": return "小雨"; - case "80": case "81": case "82": return "阵雨"; - case "95": return "雷暴"; - case "96": case "99": return "雷暴加冰雹"; - default: return "未知"; - } - } - - @Override - public String toString() { - return "Weather{" + - "cityName='" + cityName + '\'' + - ", temperature=" + temperature + - ", humidity=" + humidity + - ", windSpeed=" + windSpeed + - ", weatherCode='" + weatherCode + '\'' + - ", weather='" + getWeatherDescription() + '\'' + - '}'; - } -} diff --git a/project/src/main/java/com/example/crawler/repository/DataRepository.java b/project/src/main/java/com/example/crawler/repository/DataRepository.java deleted file mode 100644 index e76e5bc..0000000 --- a/project/src/main/java/com/example/crawler/repository/DataRepository.java +++ /dev/null @@ -1,75 +0,0 @@ -package com.example.crawler.repository; - -import com.example.crawler.model.Book; -import com.example.crawler.model.News; -import com.example.crawler.model.UniversityRank; -import com.example.crawler.model.Weather; -import java.util.ArrayList; -import java.util.List; - -public class DataRepository { - - private static DataRepository instance; - - private List books; - private List newsList; - private List rankings; - private List weatherList; - - private DataRepository() { - this.books = new ArrayList<>(); - this.newsList = new ArrayList<>(); - this.rankings = new ArrayList<>(); - this.weatherList = new ArrayList<>(); - } - - public static synchronized DataRepository getInstance() { - if (instance == null) { - instance = new DataRepository(); - } - return instance; - } - - public List getBooks() { - return new ArrayList<>(books); - } - - public void saveBooks(List books) { - this.books.clear(); - this.books.addAll(books); - } - - public List getNewsList() { - return new ArrayList<>(newsList); - } - - public void saveNewsList(List newsList) { - this.newsList.clear(); - this.newsList.addAll(newsList); - } - - public List getRankings() { - return new ArrayList<>(rankings); - } - - public void saveRankings(List rankings) { - this.rankings.clear(); - this.rankings.addAll(rankings); - } - - public List getWeatherList() { - return new ArrayList<>(weatherList); - } - - public void saveWeatherList(List weatherList) { - this.weatherList.clear(); - this.weatherList.addAll(weatherList); - } - - public void clearAll() { - books.clear(); - newsList.clear(); - rankings.clear(); - weatherList.clear(); - } -} diff --git a/project/src/main/java/com/example/crawler/service/BookAnalysisService.java b/project/src/main/java/com/example/crawler/service/BookAnalysisService.java deleted file mode 100644 index e1aebaf..0000000 --- a/project/src/main/java/com/example/crawler/service/BookAnalysisService.java +++ /dev/null @@ -1,171 +0,0 @@ -package com.example.crawler.service; - -import java.io.File; -import java.io.FileWriter; -import java.io.IOException; -import java.io.PrintWriter; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.stream.Collectors; - -import com.example.crawler.chart.ChartGenerator; -import com.example.crawler.constant.CrawlerConstants; -import com.example.crawler.model.Book; -import com.example.crawler.util.DataCleaner; - -public class BookAnalysisService { - - static { - File dir = new File(CrawlerConstants.REPORTS_DIR); - if (!dir.exists()) { - dir.mkdirs(); - } - } - - public void analyze(List books) { - if (books == null || books.isEmpty()) { - System.out.println("没有书籍数据可分析"); - return; - } - - System.out.println("\n========== 书籍数据分析 =========="); - System.out.println("共分析 " + books.size() + " 本书\n"); - - analyzePriceDistribution(books); - analyzeRatingDistribution(books); - analyzeStockStatus(books); - - generateReport(books); - } - - private void analyzePriceDistribution(List books) { - System.out.println("【价格分析】"); - List prices = new ArrayList<>(); - for (Book book : books) { - double price = DataCleaner.cleanPrice(book.getPrice()); - if (price > 0) { - prices.add(price); - } - } - - if (prices.isEmpty()) { - System.out.println("无法获取有效价格数据"); - return; - } - - double maxPrice = prices.stream().mapToDouble(Double::doubleValue).max().orElse(0); - double minPrice = prices.stream().mapToDouble(Double::doubleValue).min().orElse(0); - double avgPrice = prices.stream().mapToDouble(Double::doubleValue).average().orElse(0); - - System.out.println("最高价: £" + String.format("%.2f", maxPrice)); - System.out.println("最低价: £" + String.format("%.2f", minPrice)); - System.out.println("平均价: £" + String.format("%.2f", avgPrice)); - - Map priceRanges = new HashMap<>(); - String[] ranges = {"0-10", "10-20", "20-30", "30-40", "40-50", "50+"}; - for (String range : ranges) { - priceRanges.put(range, 0); - } - - for (Double price : prices) { - if (price < 10) priceRanges.put("0-10", priceRanges.get("0-10") + 1); - else if (price < 20) priceRanges.put("10-20", priceRanges.get("10-20") + 1); - else if (price < 30) priceRanges.put("20-30", priceRanges.get("20-30") + 1); - else if (price < 40) priceRanges.put("30-40", priceRanges.get("30-40") + 1); - else if (price < 50) priceRanges.put("40-50", priceRanges.get("40-50") + 1); - else priceRanges.put("50+", priceRanges.get("50+") + 1); - } - - System.out.println("\n价格区间分布:"); - for (Map.Entry entry : priceRanges.entrySet()) { - System.out.println(" " + entry.getKey() + ": " + entry.getValue() + " 本"); - } - - ChartGenerator.generatePriceHistogram(priceRanges, "price_histogram.png"); - } - - private void analyzeRatingDistribution(List books) { - System.out.println("\n【评分分析】"); - Map ratingCounts = new HashMap<>(); - ratingCounts.put("5星", 0); - ratingCounts.put("4星", 0); - ratingCounts.put("3星", 0); - ratingCounts.put("2星", 0); - ratingCounts.put("1星", 0); - ratingCounts.put("未知", 0); - - for (Book book : books) { - int rating = DataCleaner.cleanRating(book.getRating()); - switch (rating) { - case 5: ratingCounts.put("5星", ratingCounts.get("5星") + 1); break; - case 4: ratingCounts.put("4星", ratingCounts.get("4星") + 1); break; - case 3: ratingCounts.put("3星", ratingCounts.get("3星") + 1); break; - case 2: ratingCounts.put("2星", ratingCounts.get("2星") + 1); break; - case 1: ratingCounts.put("1星", ratingCounts.get("1星") + 1); break; - default: ratingCounts.put("未知", ratingCounts.get("未知") + 1); - } - } - - int total = books.size(); - System.out.println("评分分布:"); - for (Map.Entry entry : ratingCounts.entrySet()) { - double percentage = (entry.getValue() * 100.0) / total; - System.out.println(" " + entry.getKey() + ": " + entry.getValue() + " 本 (" + String.format("%.1f", percentage) + "%)"); - } - - ChartGenerator.generateRatingPieChart(ratingCounts, "rating_pie.png"); - } - - private void analyzeStockStatus(List books) { - System.out.println("\n【库存分析】"); - int inStock = 0; - int outOfStock = 0; - - for (Book book : books) { - String availability = book.getAvailability(); - if (availability != null && availability.toLowerCase().contains("in stock")) { - inStock++; - } else { - outOfStock++; - } - } - - System.out.println("有库存: " + inStock + " 本"); - System.out.println("缺货: " + outOfStock + " 本"); - } - - private void generateReport(List books) { - String fileName = CrawlerConstants.REPORTS_DIR + "/book_analysis_report.txt"; - try (PrintWriter writer = new PrintWriter(new FileWriter(fileName))) { - writer.println("========== 书籍数据分析报告 =========="); - writer.println("生成时间: " + java.time.LocalDateTime.now()); - writer.println("分析书籍总数: " + books.size()); - writer.println(); - - List prices = books.stream() - .map(b -> DataCleaner.cleanPrice(b.getPrice())) - .filter(p -> p > 0) - .collect(Collectors.toList()); - - if (!prices.isEmpty()) { - writer.println("【价格统计】"); - writer.println("最高价: £" + String.format("%.2f", prices.stream().mapToDouble(Double::doubleValue).max().orElse(0))); - writer.println("最低价: £" + String.format("%.2f", prices.stream().mapToDouble(Double::doubleValue).min().orElse(0))); - writer.println("平均价: £" + String.format("%.2f", prices.stream().mapToDouble(Double::doubleValue).average().orElse(0))); - writer.println(); - } - - writer.println("【库存统计】"); - long inStock = books.stream().filter(b -> b.getAvailability() != null && b.getAvailability().toLowerCase().contains("in stock")).count(); - writer.println("有库存: " + inStock + " 本"); - writer.println("缺货: " + (books.size() - inStock) + " 本"); - - writer.println("\n报告生成完成"); - System.out.println("\n报告已保存: " + fileName); - } catch (IOException e) { - System.err.println("生成报告失败: " + e.getMessage()); - } - } -} diff --git a/project/src/main/java/com/example/crawler/service/NewsAnalysisService.java b/project/src/main/java/com/example/crawler/service/NewsAnalysisService.java deleted file mode 100644 index 13b68cf..0000000 --- a/project/src/main/java/com/example/crawler/service/NewsAnalysisService.java +++ /dev/null @@ -1,138 +0,0 @@ -package com.example.crawler.service; - -import java.io.File; -import java.io.FileWriter; -import java.io.IOException; -import java.io.PrintWriter; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.stream.Collectors; - -import com.example.crawler.chart.ChartGenerator; -import com.example.crawler.constant.CrawlerConstants; -import com.example.crawler.model.News; -import com.example.crawler.util.DataCleaner; - -public class NewsAnalysisService { - - static { - File dir = new File(CrawlerConstants.REPORTS_DIR); - if (!dir.exists()) { - dir.mkdirs(); - } - } - - public void analyze(List newsList) { - if (newsList == null || newsList.isEmpty()) { - System.out.println("没有新闻数据可分析"); - return; - } - - System.out.println("\n========== 新闻数据分析 =========="); - System.out.println("共分析 " + newsList.size() + " 条新闻\n"); - - analyzeTimeDistribution(newsList); - analyzeKeywords(newsList); - - generateReport(newsList); - } - - private void analyzeTimeDistribution(List newsList) { - System.out.println("【发布时间分布】"); - Map hourDistribution = new HashMap<>(); - for (int i = 0; i < 24; i++) { - hourDistribution.put(i, 0); - } - - for (News news : newsList) { - try { - java.time.LocalDateTime dateTime = DataCleaner.cleanNewsTime(news.getPublishTime()); - int hour = DataCleaner.extractHour(dateTime); - hourDistribution.put(hour, hourDistribution.get(hour) + 1); - } catch (Exception e) { - // 忽略解析失败的数据 - } - } - - System.out.println("\n按小时统计:"); - for (int i = 0; i < 24; i++) { - int count = hourDistribution.get(i); - String bar = "*".repeat(Math.max(1, count)); - System.out.printf(" %02d:00 - %02d:00: %3d %s%n", i, (i + 1) % 24, count, bar); - } - - int peakHour = 0; - int peakCount = 0; - for (Map.Entry entry : hourDistribution.entrySet()) { - if (entry.getValue() > peakCount) { - peakCount = entry.getValue(); - peakHour = entry.getKey(); - } - } - System.out.println("\n高峰时段: " + String.format("%02d:00", peakHour) + " (发布 " + peakCount + " 条新闻)"); - - ChartGenerator.generateNewsTimeTrend(hourDistribution, "news_time_trend.png"); - } - - private void analyzeKeywords(List newsList) { - System.out.println("\n【关键词分析】"); - Map allWords = new HashMap<>(); - - for (News news : newsList) { - String title = DataCleaner.cleanTitle(news.getTitle()); - String[] words = DataCleaner.extractWords(title); - Map wordFreq = DataCleaner.countWordFrequency(words); - for (Map.Entry entry : wordFreq.entrySet()) { - allWords.put(entry.getKey(), allWords.getOrDefault(entry.getKey(), 0) + entry.getValue()); - } - } - - List> sortedWords = allWords.entrySet().stream() - .sorted(Map.Entry.comparingByValue().reversed()) - .limit(20) - .collect(Collectors.toList()); - - System.out.println("\n高频词 TOP 10:"); - for (int i = 0; i < Math.min(10, sortedWords.size()); i++) { - Map.Entry entry = sortedWords.get(i); - System.out.printf(" %2d. %s: %d%n", i + 1, entry.getKey(), entry.getValue()); - } - - Map top10 = sortedWords.stream() - .limit(10) - .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); - - ChartGenerator.generateWordFrequencyBarChart(top10, "news_top_words.png"); - } - - private void generateReport(List newsList) { - String fileName = CrawlerConstants.REPORTS_DIR + "/news_analysis_report.txt"; - try (PrintWriter writer = new PrintWriter(new FileWriter(fileName))) { - writer.println("========== 新闻数据分析报告 =========="); - writer.println("生成时间: " + java.time.LocalDateTime.now()); - writer.println("分析新闻总数: " + newsList.size()); - writer.println(); - - Map hourDistribution = new HashMap<>(); - for (int i = 0; i < 24; i++) hourDistribution.put(i, 0); - for (News news : newsList) { - try { - int hour = DataCleaner.extractHour(DataCleaner.cleanNewsTime(news.getPublishTime())); - hourDistribution.put(hour, hourDistribution.get(hour) + 1); - } catch (Exception e) {} - } - - writer.println("【发布时间分布】"); - for (int i = 0; i < 24; i++) { - writer.println(String.format(" %02d:00 - %02d:00: %d 条", i, (i + 1) % 24, hourDistribution.get(i))); - } - - writer.println("\n报告生成完成"); - System.out.println("\n报告已保存: " + fileName); - } catch (IOException e) { - System.err.println("生成报告失败: " + e.getMessage()); - } - } -} diff --git a/project/src/main/java/com/example/crawler/service/RankingAnalysisService.java b/project/src/main/java/com/example/crawler/service/RankingAnalysisService.java deleted file mode 100644 index 1259e40..0000000 --- a/project/src/main/java/com/example/crawler/service/RankingAnalysisService.java +++ /dev/null @@ -1,189 +0,0 @@ -package com.example.crawler.service; - -import java.io.File; -import java.io.FileWriter; -import java.io.IOException; -import java.io.PrintWriter; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.stream.Collectors; - -import com.example.crawler.chart.ChartGenerator; -import com.example.crawler.constant.CrawlerConstants; -import com.example.crawler.model.UniversityRank; -import com.example.crawler.util.DataCleaner; - -public class RankingAnalysisService { - - static { - File dir = new File(CrawlerConstants.REPORTS_DIR); - if (!dir.exists()) { - dir.mkdirs(); - } - } - - public void analyze(List ranks) { - if (ranks == null || ranks.isEmpty()) { - System.out.println("没有大学排名数据可分析"); - return; - } - - System.out.println("\n========== 大学排名数据分析 =========="); - System.out.println("共分析 " + ranks.size() + " 所大学\n"); - - analyzeProvinceDistribution(ranks); - analyzeScoreDistribution(ranks); - analyzeCategoryDistribution(ranks); - - generateReport(ranks); - } - - private void analyzeProvinceDistribution(List ranks) { - System.out.println("【各省份上榜大学数量】"); - Map provinceCounts = new HashMap<>(); - - for (UniversityRank rank : ranks) { - String province = rank.getProvince(); - if (province != null && !province.isEmpty()) { - provinceCounts.put(province, provinceCounts.getOrDefault(province, 0) + 1); - } - } - - List> sorted = provinceCounts.entrySet().stream() - .sorted(Map.Entry.comparingByValue().reversed()) - .collect(Collectors.toList()); - - System.out.println("\n省份排行榜 TOP 10:"); - int rankNum = 1; - for (Map.Entry entry : sorted) { - if (rankNum > 10) break; - System.out.printf(" %2d. %s: %d 所大学%n", rankNum++, entry.getKey(), entry.getValue()); - } - - Map top10 = sorted.stream() - .limit(10) - .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); - - ChartGenerator.generateProvinceBarChart(top10, "province_bar.png"); - } - - private void analyzeScoreDistribution(List ranks) { - System.out.println("\n【总分分析】"); - List scores = new ArrayList<>(); - - for (UniversityRank rank : ranks) { - double score = DataCleaner.cleanScore(rank.getTotalScore()); - if (score > 0) { - scores.add(score); - } - } - - if (scores.isEmpty()) { - System.out.println("无法获取有效分数数据"); - return; - } - - double maxScore = scores.stream().mapToDouble(Double::doubleValue).max().orElse(0); - double minScore = scores.stream().mapToDouble(Double::doubleValue).min().orElse(0); - double avgScore = scores.stream().mapToDouble(Double::doubleValue).average().orElse(0); - - List sortedScores = scores.stream().sorted().collect(Collectors.toList()); - double median = sortedScores.get(sortedScores.size() / 2); - - System.out.println("最高分: " + String.format("%.2f", maxScore)); - System.out.println("最低分: " + String.format("%.2f", minScore)); - System.out.println("平均分: " + String.format("%.2f", avgScore)); - System.out.println("中位数: " + String.format("%.2f", median)); - - Map scoreRanges = new HashMap<>(); - String[] ranges = {"0-20", "20-40", "40-60", "60-80", "80-100"}; - for (String range : ranges) { - scoreRanges.put(range, 0); - } - - for (Double score : scores) { - if (score < 20) scoreRanges.put("0-20", scoreRanges.get("0-20") + 1); - else if (score < 40) scoreRanges.put("20-40", scoreRanges.get("20-40") + 1); - else if (score < 60) scoreRanges.put("40-60", scoreRanges.get("40-60") + 1); - else if (score < 80) scoreRanges.put("60-80", scoreRanges.get("60-80") + 1); - else scoreRanges.put("80-100", scoreRanges.get("80-100") + 1); - } - - System.out.println("\n分数区间分布:"); - for (Map.Entry entry : scoreRanges.entrySet()) { - System.out.println(" " + entry.getKey() + ": " + entry.getValue() + " 所"); - } - - ChartGenerator.generateScoreHistogram(scoreRanges, "score_boxplot.png"); - } - - private void analyzeCategoryDistribution(List ranks) { - System.out.println("\n【办学层次统计】"); - Map categoryCounts = new HashMap<>(); - - for (UniversityRank rank : ranks) { - String category = rank.getCategory(); - if (category != null && !category.isEmpty()) { - categoryCounts.put(category, categoryCounts.getOrDefault(category, 0) + 1); - } - } - - if (categoryCounts.isEmpty()) { - System.out.println("没有办学层次数据"); - return; - } - - List> sorted = categoryCounts.entrySet().stream() - .sorted(Map.Entry.comparingByValue().reversed()) - .collect(Collectors.toList()); - - System.out.println("\n办学层次分布:"); - for (Map.Entry entry : sorted) { - System.out.printf(" %s: %d 所%n", entry.getKey(), entry.getValue()); - } - } - - private void generateReport(List ranks) { - String fileName = CrawlerConstants.REPORTS_DIR + "/ranking_analysis_report.txt"; - try (PrintWriter writer = new PrintWriter(new FileWriter(fileName))) { - writer.println("========== 大学排名数据分析报告 =========="); - writer.println("生成时间: " + java.time.LocalDateTime.now()); - writer.println("分析大学总数: " + ranks.size()); - writer.println(); - - Map provinceCounts = new HashMap<>(); - for (UniversityRank rank : ranks) { - String province = rank.getProvince(); - if (province != null && !province.isEmpty()) { - provinceCounts.put(province, provinceCounts.getOrDefault(province, 0) + 1); - } - } - - writer.println("【省份排行榜 TOP 10】"); - provinceCounts.entrySet().stream() - .sorted(Map.Entry.comparingByValue().reversed()) - .limit(10) - .forEach(e -> writer.println(" " + e.getKey() + ": " + e.getValue() + " 所大学")); - - List scores = ranks.stream() - .map(r -> DataCleaner.cleanScore(r.getTotalScore())) - .filter(s -> s > 0) - .collect(Collectors.toList()); - - if (!scores.isEmpty()) { - writer.println(); - writer.println("【分数统计】"); - writer.println("最高分: " + String.format("%.2f", scores.stream().mapToDouble(Double::doubleValue).max().orElse(0))); - writer.println("最低分: " + String.format("%.2f", scores.stream().mapToDouble(Double::doubleValue).min().orElse(0))); - writer.println("平均分: " + String.format("%.2f", scores.stream().mapToDouble(Double::doubleValue).average().orElse(0))); - } - - writer.println("\n报告生成完成"); - System.out.println("\n报告已保存: " + fileName); - } catch (IOException e) { - System.err.println("生成报告失败: " + e.getMessage()); - } - } -} diff --git a/project/src/main/java/com/example/crawler/service/WeatherAnalysisService.java b/project/src/main/java/com/example/crawler/service/WeatherAnalysisService.java deleted file mode 100644 index e91a0a8..0000000 --- a/project/src/main/java/com/example/crawler/service/WeatherAnalysisService.java +++ /dev/null @@ -1,163 +0,0 @@ -package com.example.crawler.service; - -import java.io.File; -import java.io.FileWriter; -import java.io.IOException; -import java.io.PrintWriter; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import com.example.crawler.chart.ChartGenerator; -import com.example.crawler.constant.CrawlerConstants; -import com.example.crawler.model.Weather; - -public class WeatherAnalysisService { - - static { - File dir = new File(CrawlerConstants.REPORTS_DIR); - if (!dir.exists()) { - dir.mkdirs(); - } - } - - public void analyze(List weatherList) { - if (weatherList == null || weatherList.isEmpty()) { - System.out.println("没有天气数据可分析"); - return; - } - - System.out.println("\n========== 天气数据分析 =========="); - System.out.println("共分析 " + weatherList.size() + " 个城市\n"); - - analyzeCurrentWeather(weatherList); - analyzeTemperatureTrend(weatherList); - analyzeHumidityTrend(weatherList); - analyzeComfortIndex(weatherList); - - generateReport(weatherList); - } - - private void analyzeCurrentWeather(List weatherList) { - System.out.println("【当前天气对比】"); - System.out.println("┌──────────┬──────────┬──────────┬──────────┬──────────┬──────────┐"); - System.out.println("│ 城市名称 │ 温度(°C)│ 湿度(%) │ 风速(km/h)│ 天气状况 │ 舒适度 │"); - System.out.println("├──────────┼──────────┼──────────┼──────────┼──────────┼──────────┤"); - - for (Weather weather : weatherList) { - double comfort = calculateComfortIndex(weather.getTemperature(), weather.getHumidity()); - String comfortDesc = getComfortDescription(comfort); - System.out.printf("│ %-8s │ %8.1f │ %8.0f │ %8.1f │ %-8s │ %-8s │%n", - weather.getCityName(), - weather.getTemperature(), - weather.getHumidity(), - weather.getWindSpeed(), - weather.getWeatherDescription(), - comfortDesc); - } - System.out.println("└──────────┴──────────┴──────────┴──────────┴──────────┴──────────┘"); - } - - private void analyzeTemperatureTrend(List weatherList) { - System.out.println("\n【未来24小时温度分析】"); - - Map> cityTemperatures = new HashMap<>(); - for (Weather weather : weatherList) { - cityTemperatures.put(weather.getCityName(), weather.getHourlyTemperatures()); - - List temps = weather.getHourlyTemperatures(); - if (!temps.isEmpty()) { - double maxTemp = temps.stream().mapToDouble(Double::doubleValue).max().orElse(0); - double minTemp = temps.stream().mapToDouble(Double::doubleValue).min().orElse(0); - double avgTemp = temps.stream().mapToDouble(Double::doubleValue).average().orElse(0); - - int maxIndex = temps.indexOf(maxTemp); - int minIndex = temps.indexOf(minTemp); - - String maxTime = maxIndex < weather.getHourlyTimes().size() ? weather.getHourlyTimes().get(maxIndex) : ""; - String minTime = minIndex < weather.getHourlyTimes().size() ? weather.getHourlyTimes().get(minIndex) : ""; - - System.out.printf(" %s: 最高 %.1f°C(%s) 最低 %.1f°C(%s) 平均 %.1f°C%n", - weather.getCityName(), maxTemp, maxTime, minTemp, minTime, avgTemp); - } - - ChartGenerator.generateTemperatureTrend( - weather.getHourlyTimes(), - weather.getHourlyTemperatures(), - weather.getCityName(), - "temperature_" + weather.getCityName() + ".png" - ); - } - - ChartGenerator.generateMultiCityTemperatureComparison(cityTemperatures, "temperature_comparison.png"); - } - - private void analyzeHumidityTrend(List weatherList) { - System.out.println("\n【未来24小时湿度分析】"); - for (Weather weather : weatherList) { - List humidities = weather.getHourlyHumidities(); - if (!humidities.isEmpty()) { - double avgHumidity = humidities.stream().mapToInt(Integer::intValue).average().orElse(0); - System.out.printf(" %s: 平均湿度 %.0f%%%n", weather.getCityName(), avgHumidity); - } - } - } - - private void analyzeComfortIndex(List weatherList) { - System.out.println("\n【舒适度指数分析】"); - System.out.println("(基于温度和湿度的体感舒适度计算,0-100分制)"); - - for (Weather weather : weatherList) { - double comfort = calculateComfortIndex(weather.getTemperature(), weather.getHumidity()); - String description = getComfortDescription(comfort); - System.out.printf(" %s: %.1f分 (%s)%n", weather.getCityName(), comfort, description); - } - } - - private double calculateComfortIndex(double temperature, double humidity) { - double tempDiff = Math.abs(temperature - 22); - double humDiff = Math.abs(humidity - 50); - - double comfort = 100 - (tempDiff * 3 + humDiff * 0.5); - return Math.max(0, Math.min(100, comfort)); - } - - private String getComfortDescription(double comfort) { - if (comfort >= 80) return "非常舒适"; - if (comfort >= 60) return "舒适"; - if (comfort >= 40) return "一般"; - if (comfort >= 20) return "不舒适"; - return "极不舒适"; - } - - private void generateReport(List weatherList) { - String fileName = CrawlerConstants.REPORTS_DIR + "/weather_analysis_report.txt"; - try (PrintWriter writer = new PrintWriter(new FileWriter(fileName))) { - writer.println("========== 天气数据分析报告 =========="); - writer.println("生成时间: " + java.time.LocalDateTime.now()); - writer.println("分析城市数量: " + weatherList.size()); - writer.println("数据来源: Open-Meteo API (CC BY 4.0)"); - writer.println(); - - writer.println("【多城市天气对比】"); - for (Weather weather : weatherList) { - writer.println("\n城市: " + weather.getCityName()); - writer.println(" 当前温度: " + String.format("%.1f°C", weather.getTemperature())); - writer.println(" 当前湿度: " + String.format("%.0f%%", weather.getHumidity())); - writer.println(" 风速: " + String.format("%.1f km/h", weather.getWindSpeed())); - writer.println(" 天气: " + weather.getWeatherDescription()); - - List temps = weather.getHourlyTemperatures(); - if (!temps.isEmpty()) { - writer.println(" 24小时平均温度: " + String.format("%.1f°C", temps.stream().mapToDouble(Double::doubleValue).average().orElse(0))); - } - } - - writer.println("\n报告生成完成"); - System.out.println("\n报告已保存: " + fileName); - } catch (IOException e) { - System.err.println("生成报告失败: " + e.getMessage()); - } - } -} diff --git a/project/src/main/java/com/example/crawler/strategy/BookCrawlStrategy.java b/project/src/main/java/com/example/crawler/strategy/BookCrawlStrategy.java deleted file mode 100644 index 746b57e..0000000 --- a/project/src/main/java/com/example/crawler/strategy/BookCrawlStrategy.java +++ /dev/null @@ -1,127 +0,0 @@ -package com.example.crawler.strategy; - -import java.util.ArrayList; -import java.util.List; -import java.util.Map; - -import org.jsoup.Jsoup; -import org.jsoup.nodes.Document; -import org.jsoup.nodes.Element; -import org.jsoup.select.Elements; - -import com.example.crawler.exception.CrawlException; -import com.example.crawler.exception.NetworkException; -import com.example.crawler.exception.ParseException; -import com.example.crawler.model.Book; -import com.example.crawler.util.HttpUtil; - -/** - * 书籍爬取策略 - * // 策略模式:书籍信息爬取策略 - */ -public class BookCrawlStrategy implements CrawlStrategy { - - private static final String BASE_URL = "https://books.toscrape.com/"; - private static final String PAGE_URL_FORMAT = "https://books.toscrape.com/catalogue/page-%d.html"; - private static final int MAX_PAGES = 30; // 最大爬取页数 - - @Override - public List crawl() throws CrawlException { - List books = new ArrayList<>(); - int pageNum = 1; - - try { - while (true) { - // 达到最大页数限制时停止 - if (pageNum > MAX_PAGES) { - System.out.println("已达到最大爬取页数限制(" + MAX_PAGES + "页),停止爬取"); - break; - } - - String url = pageNum == 1 ? BASE_URL : String.format(PAGE_URL_FORMAT, pageNum); - - // 设置请求头 - Map headers = Map.of( - "User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36", - "Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8" - ); - - String html = HttpUtil.get(url, headers); - Document doc = Jsoup.parse(html); - - Elements bookElements = doc.select(".product_pod"); - - // 如果没有书籍元素,说明已到达最后一页 - if (bookElements.isEmpty()) { - System.out.println("第 " + pageNum + " 页没有书籍数据,停止爬取"); - break; - } - - for (Element bookElement : bookElements) { - Book book = parseBook(bookElement); - books.add(book); - } - - System.out.println("已爬取第 " + pageNum + " 页,共 " + books.size() + " 本书"); - - // 设置请求间隔 - HttpUtil.sleep(1); - - pageNum++; - } - - return books; - } catch (NetworkException e) { - // 如果是404错误且已经爬取了一些数据,返回已获取的数据 - if (e.getMessage().contains("404") && !books.isEmpty()) { - System.out.println("第 " + pageNum + " 页不存在(404),返回已爬取的 " + books.size() + " 本书"); - return books; - } - throw new NetworkException("爬取书籍信息时网络异常: " + e.getMessage(), e); - } catch (ParseException e) { - throw new ParseException("解析书籍信息时异常: " + e.getMessage(), e); - } catch (Exception e) { - throw new CrawlException("爬取书籍信息时发生未知异常: " + e.getMessage(), e); - } - } - - /** - * 解析书籍元素 - */ - private Book parseBook(Element bookElement) throws ParseException { - try { - // 获取书名 - Element titleElement = bookElement.selectFirst("h3 a"); - String title = titleElement != null ? titleElement.attr("title") : "未知书名"; - - // 获取价格 - Element priceElement = bookElement.selectFirst(".price_color"); - String price = priceElement != null ? priceElement.text() : "未知价格"; - - // 获取库存状态 - Element availabilityElement = bookElement.selectFirst(".instock.availability"); - String availability = availabilityElement != null ? availabilityElement.text().trim() : "未知库存"; - - // 获取星级评分 - Element ratingElement = bookElement.selectFirst(".star-rating"); - String rating = "未知"; - if (ratingElement != null) { - String classAttr = ratingElement.attr("class"); - if (classAttr.contains("One")) rating = "1星"; - else if (classAttr.contains("Two")) rating = "2星"; - else if (classAttr.contains("Three")) rating = "3星"; - else if (classAttr.contains("Four")) rating = "4星"; - else if (classAttr.contains("Five")) rating = "5星"; - } - - return new Book(title, price, availability, rating); - } catch (Exception e) { - throw new ParseException("解析书籍信息失败: " + e.getMessage(), e); - } - } - - @Override - public String getDataSourceName() { - return "toscrape.com书籍信息"; - } -} \ No newline at end of file diff --git a/project/src/main/java/com/example/crawler/strategy/CrawlStrategy.java b/project/src/main/java/com/example/crawler/strategy/CrawlStrategy.java deleted file mode 100644 index 39f8d22..0000000 --- a/project/src/main/java/com/example/crawler/strategy/CrawlStrategy.java +++ /dev/null @@ -1,27 +0,0 @@ -package com.example.crawler.strategy; - -import com.example.crawler.exception.CrawlException; - -import java.util.List; - -/** - * 爬取策略接口 - * 定义爬取操作的标准方法,实现策略模式 - */ -public interface CrawlStrategy { - - /** - * 执行爬取操作 - * - * @return 爬取到的数据列表 - * @throws CrawlException 爬虫异常 - */ - List crawl() throws CrawlException; - - /** - * 获取数据源名称 - * - * @return 数据源名称 - */ - String getDataSourceName(); -} \ No newline at end of file diff --git a/project/src/main/java/com/example/crawler/strategy/NewsCrawlStrategy.java b/project/src/main/java/com/example/crawler/strategy/NewsCrawlStrategy.java deleted file mode 100644 index ac30f8e..0000000 --- a/project/src/main/java/com/example/crawler/strategy/NewsCrawlStrategy.java +++ /dev/null @@ -1,151 +0,0 @@ -package com.example.crawler.strategy; - -import java.util.ArrayList; -import java.util.List; -import java.util.Map; - -import org.jsoup.Jsoup; -import org.jsoup.nodes.Document; -import org.jsoup.nodes.Element; -import org.jsoup.select.Elements; - -import com.example.crawler.exception.CrawlException; -import com.example.crawler.exception.NetworkException; -import com.example.crawler.exception.ParseException; -import com.example.crawler.model.News; -import com.example.crawler.util.HttpUtil; - -/** - * 新浪新闻爬取策略 - * // 策略模式:新浪新闻爬取策略 - */ -public class NewsCrawlStrategy implements CrawlStrategy { - - private static final String NEWS_URL = "https://news.sina.com.cn/china/"; - private static final int MAX_NEWS_COUNT = 20; - - @Override - public List crawl() throws CrawlException { - List newsList = new ArrayList<>(); - - try { - // 设置请求头 - Map headers = Map.of( - "User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36", - "Referer", "https://news.sina.com.cn/", - "Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8" - ); - - String html = HttpUtil.get(NEWS_URL, headers); - Document doc = Jsoup.parse(html); - - // 新浪新闻页面结构可能变化,使用多种选择器尝试 - Elements newsElements = doc.select(".news-item, .news-list li, .list-item, .feed-card-item"); - - // 如果上述选择器都没找到,尝试更通用的选择器 - if (newsElements.isEmpty()) { - newsElements = doc.select("a[href*=sina.com.cn]"); - } - - int count = 0; - for (Element element : newsElements) { - if (count >= MAX_NEWS_COUNT) { - break; - } - - try { - News news = parseNews(element); - if (news != null && news.getTitle() != null && !news.getTitle().isEmpty()) { - newsList.add(news); - count++; - } - } catch (ParseException e) { - // 跳过解析失败的新闻,继续处理下一个 - continue; - } - } - - // 如果使用通用选择器获取的结果不够,尝试另一种方式 - if (newsList.size() < MAX_NEWS_COUNT) { - Elements titleElements = doc.select("h2 a, h3 a, .title a, .news-title a"); - for (Element element : titleElements) { - if (count >= MAX_NEWS_COUNT) { - break; - } - try { - News news = parseNewsFromTitleElement(element); - if (news != null && news.getTitle() != null && !news.getTitle().isEmpty()) { - newsList.add(news); - count++; - } - } catch (ParseException e) { - continue; - } - } - } - - System.out.println("已爬取 " + newsList.size() + " 条新浪新闻"); - return newsList; - - } catch (NetworkException e) { - throw new NetworkException("爬取新浪新闻时网络异常: " + e.getMessage(), e); - } catch (Exception e) { - throw new CrawlException("爬取新浪新闻时发生未知异常: " + e.getMessage(), e); - } - } - - /** - * 解析新闻元素 - */ - private News parseNews(Element element) throws ParseException { - try { - String title = ""; - String url = ""; - String publishTime = ""; - - // 尝试获取标题和链接 - Element linkElement = element.selectFirst("a"); - if (linkElement != null) { - title = linkElement.text().trim(); - url = linkElement.attr("abs:href"); - } - - // 尝试获取发布时间 - Element timeElement = element.selectFirst(".time, .pubtime, span[class*=time]"); - if (timeElement != null) { - publishTime = timeElement.text().trim(); - } - - if (title.isEmpty() || url.isEmpty()) { - return null; - } - - return new News(title, publishTime, url); - } catch (Exception e) { - throw new ParseException("解析新闻信息失败: " + e.getMessage(), e); - } - } - - /** - * 从标题元素解析新闻 - */ - private News parseNewsFromTitleElement(Element element) throws ParseException { - try { - String title = element.text().trim(); - String url = element.attr("abs:href"); - - if (title.isEmpty() || url.isEmpty()) { - return null; - } - - return new News(title, "", url); - } catch (Exception e) { - throw new ParseException("解析新闻标题失败: " + e.getMessage(), e); - } - } - - @Override - public String getDataSourceName() { - return "新浪国内新闻"; - } -} \ No newline at end of file diff --git a/project/src/main/java/com/example/crawler/strategy/StrategyFactory.java b/project/src/main/java/com/example/crawler/strategy/StrategyFactory.java deleted file mode 100644 index 9b56383..0000000 --- a/project/src/main/java/com/example/crawler/strategy/StrategyFactory.java +++ /dev/null @@ -1,24 +0,0 @@ -package com.example.crawler.strategy; - -import com.example.crawler.strategy.BookCrawlStrategy; -import com.example.crawler.strategy.NewsCrawlStrategy; -import com.example.crawler.strategy.UniversityRankCrawlStrategy; -import com.example.crawler.strategy.WeatherCrawlStrategy; - -public class StrategyFactory { - - public static CrawlStrategy getStrategy(int choice) { - switch (choice) { - case 1: - return new BookCrawlStrategy(); - case 2: - return new NewsCrawlStrategy(); - case 3: - return new UniversityRankCrawlStrategy(); - case 4: - return new WeatherCrawlStrategy(); - default: - throw new IllegalArgumentException("Invalid choice: " + choice); - } - } -} diff --git a/project/src/main/java/com/example/crawler/strategy/UniversityRankCrawlStrategy.java b/project/src/main/java/com/example/crawler/strategy/UniversityRankCrawlStrategy.java deleted file mode 100644 index f4b5f81..0000000 --- a/project/src/main/java/com/example/crawler/strategy/UniversityRankCrawlStrategy.java +++ /dev/null @@ -1,148 +0,0 @@ -package com.example.crawler.strategy; - -import java.util.ArrayList; -import java.util.List; -import java.util.Map; - -import org.jsoup.Jsoup; -import org.jsoup.nodes.Document; -import org.jsoup.nodes.Element; -import org.jsoup.select.Elements; - -import com.example.crawler.exception.CrawlException; -import com.example.crawler.exception.NetworkException; -import com.example.crawler.exception.ParseException; -import com.example.crawler.model.UniversityRank; -import com.example.crawler.util.HttpUtil; - -/** - * 软科中国大学排名爬取策略 - * // 策略模式:软科中国大学排名爬取策略 - */ -public class UniversityRankCrawlStrategy implements CrawlStrategy { - - private static final String RANKING_URL = "https://www.shanghairanking.cn/rankings/bcur/2025"; - - @Override - public List crawl() throws CrawlException { - List rankings = new ArrayList<>(); - - try { - // 设置请求头 - Map headers = Map.of( - "User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36", - "Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8", - "Referer", "https://www.shanghairanking.cn/" - ); - - // 设置请求延迟 - HttpUtil.sleep(3); - - String html = HttpUtil.get(RANKING_URL, headers); - Document doc = Jsoup.parse(html); - - // 提取表格数据 - Elements rows = doc.select("table tbody tr"); - - if (rows.isEmpty()) { - // 如果第一个选择器失败,尝试其他可能的选择器 - rows = doc.select(".rk-table tbody tr"); - } - - if (rows.isEmpty()) { - // 尝试更通用的选择器 - rows = doc.select("tr"); - } - - int count = 0; - for (Element row : rows) { - try { - UniversityRank ranking = parseRow(row); - if (ranking != null && ranking.getRank() != null) { - rankings.add(ranking); - count++; - - // 最多爬取200条数据 - if (count >= 200) { - break; - } - } - } catch (ParseException e) { - // 跳过解析失败的行 - continue; - } - } - - System.out.println("已爬取 " + rankings.size() + " 条大学排名数据"); - return rankings; - - } catch (NetworkException e) { - throw new NetworkException("爬取软科大学排名时网络异常: " + e.getMessage(), e); - } catch (Exception e) { - throw new CrawlException("爬取软科大学排名时发生未知异常: " + e.getMessage(), e); - } - } - - /** - * 解析表格行数据 - */ - private UniversityRank parseRow(Element row) throws ParseException { - try { - Elements cells = row.select("td"); - - if (cells.size() < 4) { - return null; - } - - // 第1列:排名 - String rankStr = cells.get(0).text().trim(); - Integer rank = null; - try { - rank = Integer.parseInt(rankStr); - } catch (NumberFormatException e) { - // 如果排名不是数字(如"1-3"这样的范围),尝试提取第一个数字 - String numPart = rankStr.replaceAll("[^0-9]", ""); - if (!numPart.isEmpty()) { - rank = Integer.parseInt(numPart); - } - } - - if (rank == null) { - return null; - } - - // 第2列:学校名称 - String universityName = cells.get(1).text().trim(); - - // 第4列:总分 - String totalScore = ""; - if (cells.size() > 3) { - totalScore = cells.get(3).text().trim(); - } - - // 尝试提取省份和办学层次(第3列可能包含这些信息) - String province = ""; - String category = ""; - if (cells.size() > 2) { - String thirdColumn = cells.get(2).text().trim(); - // 尝试解析省份和办学层次 - String[] parts = thirdColumn.split("\\s+"); - if (parts.length >= 1) { - province = parts[0]; - } - if (parts.length >= 2) { - category = parts[1]; - } - } - - return new UniversityRank(rank, universityName, totalScore, province, category); - } catch (Exception e) { - throw new ParseException("解析大学排名行数据失败: " + e.getMessage(), e); - } - } - - @Override - public String getDataSourceName() { - return "软科中国大学排名"; - } -} \ No newline at end of file diff --git a/project/src/main/java/com/example/crawler/strategy/WeatherCrawlStrategy.java b/project/src/main/java/com/example/crawler/strategy/WeatherCrawlStrategy.java deleted file mode 100644 index 6ade78c..0000000 --- a/project/src/main/java/com/example/crawler/strategy/WeatherCrawlStrategy.java +++ /dev/null @@ -1,177 +0,0 @@ -package com.example.crawler.strategy; - -import java.util.ArrayList; -import java.util.List; -import java.util.Map; - -import com.example.crawler.constant.CrawlerConstants; -import com.example.crawler.exception.CrawlException; -import com.example.crawler.exception.NetworkException; -import com.example.crawler.exception.ParseException; -import com.example.crawler.model.Weather; -import com.example.crawler.util.HttpUtil; -import com.google.gson.JsonArray; -import com.google.gson.JsonElement; -import com.google.gson.JsonObject; -import com.google.gson.JsonParser; - -public class WeatherCrawlStrategy implements CrawlStrategy { - - @Override - public List crawl() throws CrawlException { - List weatherList = new ArrayList<>(); - - try { - for (Map.Entry entry : CrawlerConstants.CITY_COORDINATES.entrySet()) { - String cityName = entry.getKey(); - double[] coords = entry.getValue(); - double latitude = coords[0]; - double longitude = coords[1]; - - String weatherUrl = buildApiUrl(latitude, longitude); - Map headers = Map.of( - "User-Agent", CrawlerConstants.USER_AGENT - ); - - String response = HttpUtil.get(weatherUrl, headers); - Weather weather = parseWeatherData(cityName, response); - weatherList.add(weather); - - System.out.println("已获取 " + cityName + " 的天气信息"); - - HttpUtil.sleep(2); - } - - return weatherList; - - } catch (NetworkException e) { - throw new NetworkException("爬取天气数据时网络异常: " + e.getMessage(), e); - } catch (ParseException e) { - throw new ParseException("解析天气数据时异常: " + e.getMessage(), e); - } catch (Exception e) { - throw new CrawlException("爬取天气数据时发生未知异常: " + e.getMessage(), e); - } - } - - private String buildApiUrl(double latitude, double longitude) { - return CrawlerConstants.URL_WEATHER_API + "?latitude=" + latitude + - "&longitude=" + longitude + - "¤t_weather=true" + - "&hourly=temperature_2m,relative_humidity_2m,wind_speed_10m" + - "&forecast_days=1" + - "&timezone=Asia/Shanghai"; - } - - private Weather parseWeatherData(String cityName, String jsonData) throws ParseException { - try { - JsonObject obj = JsonParser.parseString(jsonData).getAsJsonObject(); - - Weather weather = new Weather(); - weather.setCityName(cityName); - - JsonObject currentWeather = obj.getAsJsonObject("current_weather"); - if (currentWeather != null) { - weather.setTemperature(cleanTemperature(getJsonDouble(currentWeather, "temperature", 0))); - weather.setWindSpeed(cleanWindSpeed(getJsonDouble(currentWeather, "windspeed", 0))); - weather.setWeatherCode(String.valueOf(getJsonInt(currentWeather, "weathercode", -1))); - } - - JsonObject hourly = obj.getAsJsonObject("hourly"); - if (hourly != null) { - JsonArray times = hourly.getAsJsonArray("time"); - JsonArray temps = hourly.getAsJsonArray("temperature_2m"); - JsonArray humidities = hourly.getAsJsonArray("relative_humidity_2m"); - JsonArray windSpeeds = hourly.getAsJsonArray("wind_speed_10m"); - - if (times != null && temps != null) { - int count = Math.min(times.size(), 24); - for (int i = 0; i < count; i++) { - weather.getHourlyTimes().add(cleanTimeString(getJsonString(times, i, ""))); - weather.getHourlyTemperatures().add(cleanTemperature(getJsonDouble(temps, i, 0))); - } - } - - if (humidities != null) { - int count = Math.min(humidities.size(), 24); - for (int i = 0; i < count; i++) { - weather.getHourlyHumidities().add(cleanHumidity(getJsonInt(humidities, i, 50))); - } - } - - if (windSpeeds != null) { - int count = Math.min(windSpeeds.size(), 24); - for (int i = 0; i < count; i++) { - weather.getHourlyWindSpeeds().add(cleanWindSpeed(getJsonDouble(windSpeeds, i, 0))); - } - } - - if (!weather.getHourlyHumidities().isEmpty()) { - weather.setHumidity(weather.getHourlyHumidities().get(0)); - } - } - - return weather; - } catch (Exception e) { - throw new ParseException("解析天气JSON数据失败: " + e.getMessage(), e); - } - } - - private String getJsonString(JsonArray arr, int index, String defaultValue) { - if (arr == null || index >= arr.size()) return defaultValue; - JsonElement element = arr.get(index); - return element.isJsonNull() ? defaultValue : element.getAsString(); - } - - private double getJsonDouble(JsonObject obj, String key, double defaultValue) { - JsonElement element = obj.get(key); - if (element == null || element.isJsonNull()) return defaultValue; - return element.getAsDouble(); - } - - private int getJsonInt(JsonObject obj, String key, int defaultValue) { - JsonElement element = obj.get(key); - if (element == null || element.isJsonNull()) return defaultValue; - return element.getAsInt(); - } - - private double getJsonDouble(JsonArray arr, int index, double defaultValue) { - if (arr == null || index >= arr.size()) return defaultValue; - JsonElement element = arr.get(index); - if (element == null || element.isJsonNull()) return defaultValue; - return element.getAsDouble(); - } - - private int getJsonInt(JsonArray arr, int index, int defaultValue) { - if (arr == null || index >= arr.size()) return defaultValue; - JsonElement element = arr.get(index); - if (element == null || element.isJsonNull()) return defaultValue; - return element.getAsInt(); - } - - private double cleanTemperature(double temp) { - return Math.round(temp * 10.0) / 10.0; - } - - private double cleanWindSpeed(double speed) { - return Math.round(speed * 10.0) / 10.0; - } - - private int cleanHumidity(int humidity) { - if (humidity < 0) return 50; - if (humidity > 100) return 100; - return humidity; - } - - private String cleanTimeString(String time) { - if (time == null || time.isEmpty()) return ""; - if (time.contains("T")) { - return time.substring(time.indexOf("T") + 1, time.indexOf("T") + 6); - } - return time; - } - - @Override - public String getDataSourceName() { - return "Open-Meteo 实时天气"; - } -} diff --git a/project/src/main/java/com/example/crawler/util/DataCleaner.java b/project/src/main/java/com/example/crawler/util/DataCleaner.java deleted file mode 100644 index 32991fd..0000000 --- a/project/src/main/java/com/example/crawler/util/DataCleaner.java +++ /dev/null @@ -1,122 +0,0 @@ -package com.example.crawler.util; - -import java.time.LocalDateTime; -import java.time.format.DateTimeFormatter; -import java.util.HashMap; -import java.util.Map; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -/** - * 数据清洗工具类 - * 提供各类数据的清洗方法 - */ -public class DataCleaner { - - private static final Map STOP_WORDS = new HashMap<>(); - static { - STOP_WORDS.put("的", "的"); - STOP_WORDS.put("了", "了"); - STOP_WORDS.put("是", "是"); - STOP_WORDS.put("在", "在"); - STOP_WORDS.put("和", "和"); - STOP_WORDS.put("与", "与"); - STOP_WORDS.put("对", "对"); - STOP_WORDS.put("为", "为"); - STOP_WORDS.put("有", "有"); - STOP_WORDS.put("我", "我"); - STOP_WORDS.put("你", "你"); - STOP_WORDS.put("他", "他"); - STOP_WORDS.put("她", "她"); - STOP_WORDS.put("它", "它"); - STOP_WORDS.put("这", "这"); - STOP_WORDS.put("那", "那"); - STOP_WORDS.put("就", "就"); - STOP_WORDS.put("也", "也"); - STOP_WORDS.put("都", "都"); - STOP_WORDS.put("要", "要"); - STOP_WORDS.put("会", "会"); - STOP_WORDS.put("能", "能"); - STOP_WORDS.put("可", "可"); - STOP_WORDS.put("以", "以"); - STOP_WORDS.put("说", "说"); - STOP_WORDS.put("到", "到"); - STOP_WORDS.put("来", "来"); - STOP_WORDS.put("去", "去"); - STOP_WORDS.put("着", "着"); - STOP_WORDS.put("过", "过"); - } - - public static double cleanPrice(String price) { - if (price == null || price.isEmpty()) return 0.0; - String cleaned = price.replaceAll("[^0-9.]", ""); - try { - return Double.parseDouble(cleaned); - } catch (NumberFormatException e) { - return 0.0; - } - } - - public static int cleanRating(String ratingClass) { - if (ratingClass == null) return 0; - if (ratingClass.contains("Five")) return 5; - if (ratingClass.contains("Four")) return 4; - if (ratingClass.contains("Three")) return 3; - if (ratingClass.contains("Two")) return 2; - if (ratingClass.contains("One")) return 1; - return 0; - } - - public static LocalDateTime cleanNewsTime(String timeStr) { - if (timeStr == null || timeStr.isEmpty()) return LocalDateTime.now(); - try { - DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); - return LocalDateTime.parse(timeStr, formatter); - } catch (Exception e) { - try { - DateTimeFormatter formatter2 = DateTimeFormatter.ofPattern("yyyy年MM月dd日 HH:mm"); - return LocalDateTime.parse(timeStr, formatter2); - } catch (Exception e2) { - return LocalDateTime.now(); - } - } - } - - public static String cleanTitle(String title) { - if (title == null) return ""; - return title.trim().replaceAll("\\s+", " "); - } - - public static double cleanScore(String score) { - if (score == null || score.isEmpty()) return 0.0; - String cleaned = score.replaceAll("[^0-9.]", ""); - try { - return Double.parseDouble(cleaned); - } catch (NumberFormatException e) { - return 0.0; - } - } - - public static String[] extractWords(String text) { - if (text == null || text.isEmpty()) return new String[0]; - String cleaned = text.replaceAll("[^\u4e00-\u9fa5a-zA-Z0-9]", " "); - return cleaned.split("\\s+"); - } - - public static boolean isStopWord(String word) { - return word == null || word.length() < 2 || STOP_WORDS.containsKey(word); - } - - public static Map countWordFrequency(String[] words) { - Map frequency = new HashMap<>(); - for (String word : words) { - if (isStopWord(word)) continue; - frequency.put(word, frequency.getOrDefault(word, 0) + 1); - } - return frequency; - } - - public static int extractHour(LocalDateTime dateTime) { - return dateTime.getHour(); - } -} diff --git a/project/src/main/java/com/example/crawler/util/HttpUtil.java b/project/src/main/java/com/example/crawler/util/HttpUtil.java deleted file mode 100644 index 774d2e6..0000000 --- a/project/src/main/java/com/example/crawler/util/HttpUtil.java +++ /dev/null @@ -1,126 +0,0 @@ -package com.example.crawler.util; - -import com.example.crawler.exception.NetworkException; - -import java.net.URI; -import java.net.http.HttpClient; -import java.net.http.HttpRequest; -import java.net.http.HttpResponse; -import java.time.Duration; -import java.util.Map; - -/** - * HTTP工具类 - * 封装HTTP请求操作,使用Java 11内置HttpClient - */ -public class HttpUtil { - - private static final HttpClient httpClient = HttpClient.newBuilder() - .connectTimeout(Duration.ofSeconds(30)) - .followRedirects(HttpClient.Redirect.NORMAL) - .build(); - - private static final String DEFAULT_USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36"; - - /** - * 发送GET请求 - * - * @param url 请求URL - * @return 响应内容 - * @throws NetworkException 网络异常 - */ - public static String get(String url) throws NetworkException { - return get(url, Map.of()); - } - - /** - * 发送GET请求(带请求头) - * - * @param url 请求URL - * @param headers 请求头 - * @return 响应内容 - * @throws NetworkException 网络异常 - */ - public static String get(String url, Map headers) throws NetworkException { - try { - HttpRequest.Builder requestBuilder = HttpRequest.newBuilder() - .uri(URI.create(url)) - .timeout(Duration.ofSeconds(30)) - .GET(); - - // 添加默认User-Agent - if (!headers.containsKey("User-Agent")) { - requestBuilder.header("User-Agent", DEFAULT_USER_AGENT); - } - - // 添加自定义请求头 - headers.forEach(requestBuilder::header); - - HttpRequest request = requestBuilder.build(); - HttpResponse response = httpClient.send(request, HttpResponse.BodyHandlers.ofString()); - - if (response.statusCode() != 200) { - throw new NetworkException("HTTP请求失败,状态码: " + response.statusCode()); - } - - return response.body(); - } catch (NetworkException e) { - throw e; - } catch (Exception e) { - throw new NetworkException("网络请求失败: " + e.getMessage(), e); - } - } - - /** - * 发送POST请求 - * - * @param url 请求URL - * @param body 请求体 - * @param headers 请求头 - * @return 响应内容 - * @throws NetworkException 网络异常 - */ - public static String post(String url, String body, Map headers) throws NetworkException { - try { - HttpRequest.Builder requestBuilder = HttpRequest.newBuilder() - .uri(URI.create(url)) - .timeout(Duration.ofSeconds(30)) - .header("Content-Type", "application/json") - .POST(HttpRequest.BodyPublishers.ofString(body)); - - // 添加默认User-Agent - if (!headers.containsKey("User-Agent")) { - requestBuilder.header("User-Agent", DEFAULT_USER_AGENT); - } - - // 添加自定义请求头 - headers.forEach(requestBuilder::header); - - HttpRequest request = requestBuilder.build(); - HttpResponse response = httpClient.send(request, HttpResponse.BodyHandlers.ofString()); - - if (response.statusCode() != 200) { - throw new NetworkException("HTTP请求失败,状态码: " + response.statusCode()); - } - - return response.body(); - } catch (NetworkException e) { - throw e; - } catch (Exception e) { - throw new NetworkException("网络请求失败: " + e.getMessage(), e); - } - } - - /** - * 设置请求间隔,避免对服务器造成压力 - * - * @param seconds 间隔秒数 - */ - public static void sleep(int seconds) { - try { - Thread.sleep(seconds * 1000L); - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - } - } -} \ No newline at end of file diff --git a/project/src/main/java/com/example/crawler/util/JsonUtil.java b/project/src/main/java/com/example/crawler/util/JsonUtil.java deleted file mode 100644 index 5b2e929..0000000 --- a/project/src/main/java/com/example/crawler/util/JsonUtil.java +++ /dev/null @@ -1,95 +0,0 @@ -package com.example.crawler.util; - -import com.example.crawler.exception.DataSaveException; -import com.google.gson.Gson; -import com.google.gson.GsonBuilder; - -import java.io.FileWriter; -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.util.List; - -/** - * JSON工具类 - * 封装JSON序列化和文件读写操作 - */ -public class JsonUtil { - - private static final Gson gson = new GsonBuilder() - .setPrettyPrinting() - .disableHtmlEscaping() - .create(); - - /** - * 将对象序列化为JSON字符串 - * - * @param obj 对象 - * @return JSON字符串 - */ - public static String toJson(Object obj) { - return gson.toJson(obj); - } - - /** - * 将JSON字符串反序列化为对象 - * - * @param json JSON字符串 - * @param classOfT 目标类 - * @param 泛型类型 - * @return 反序列化后的对象 - */ - public static T fromJson(String json, Class classOfT) { - return gson.fromJson(json, classOfT); - } - - /** - * 将对象保存为JSON文件 - * - * @param obj 对象 - * @param filePath 文件路径 - * @throws DataSaveException 数据保存异常 - */ - public static void saveToJsonFile(Object obj, String filePath) throws DataSaveException { - try { - // 确保目录存在 - Path path = Paths.get(filePath); - Path parentDir = path.getParent(); - if (parentDir != null && !Files.exists(parentDir)) { - Files.createDirectories(parentDir); - } - - try (FileWriter writer = new FileWriter(filePath)) { - gson.toJson(obj, writer); - } - } catch (IOException e) { - throw new DataSaveException("保存JSON文件失败: " + e.getMessage(), e); - } - } - - /** - * 将列表保存为JSON文件 - * - * @param list 列表 - * @param filePath 文件路径 - * @param 泛型类型 - * @throws DataSaveException 数据保存异常 - */ - public static void saveListToJsonFile(List list, String filePath) throws DataSaveException { - try { - // 确保目录存在 - Path path = Paths.get(filePath); - Path parentDir = path.getParent(); - if (parentDir != null && !Files.exists(parentDir)) { - Files.createDirectories(parentDir); - } - - try (FileWriter writer = new FileWriter(filePath)) { - gson.toJson(list, writer); - } - } catch (IOException e) { - throw new DataSaveException("保存JSON文件失败: " + e.getMessage(), e); - } - } -} \ No newline at end of file diff --git a/project/src/main/java/com/example/crawler/view/CrawlerView.java b/project/src/main/java/com/example/crawler/view/CrawlerView.java deleted file mode 100644 index 5e72292..0000000 --- a/project/src/main/java/com/example/crawler/view/CrawlerView.java +++ /dev/null @@ -1,72 +0,0 @@ -package com.example.crawler.view; - -import java.util.Scanner; - -/** - * 爬虫视图类 - * // MVC模式:View层,负责CLI界面显示和用户交互 - */ -public class CrawlerView { - - /** - * 显示主菜单 - */ - public void showMenu() { - System.out.println("\n=== 数据爬取与分析系统 ==="); - System.out.println("1. 爬取书籍信息(toscrape.com)"); - System.out.println("2. 爬取新浪国内新闻"); - System.out.println("3. 爬取软科中国大学排名"); - System.out.println("4. 爬取Open-Meteo实时天气"); - System.out.println("5. 爬取全部数据并保存"); - System.out.println("6. 保存当前数据到文件"); - System.out.println("7. 生成所有数据源的分析报告与图表"); - System.out.println("8. 爬取并分析所有数据(一键完成)"); - System.out.println("9. 退出"); - System.out.print("请选择操作:"); - } - - /** - * 获取用户输入 - * - * @param scanner 输入扫描器 - * @return 用户选择的数字 - */ - public int getInput(Scanner scanner) { - try { - String input = scanner.nextLine().trim(); - return Integer.parseInt(input); - } catch (NumberFormatException e) { - return -1; // 返回无效值 - } - } - - /** - * 显示错误信息 - * - * @param message 错误信息 - */ - public void showError(String message) { - System.err.println("错误: " + message); - } - - /** - * 显示成功信息 - * - * @param message 成功信息 - */ - public void showSuccess(String message) { - System.out.println("成功: " + message); - } - - /** - * 暂停并等待用户按回车键继续 - * - * @param scanner 输入扫描器 - */ - public void pause(Scanner scanner) { - System.out.print("\n按回车键继续..."); - scanner.nextLine(); - System.out.print("\033[H\033[2J"); - System.out.flush(); - } -} diff --git a/project/target/classes/com/example/crawler/Main.class b/project/target/classes/com/example/crawler/Main.class deleted file mode 100644 index 1497663..0000000 Binary files a/project/target/classes/com/example/crawler/Main.class and /dev/null differ diff --git a/project/target/classes/com/example/crawler/chart/ChartGenerator.class b/project/target/classes/com/example/crawler/chart/ChartGenerator.class deleted file mode 100644 index dd49de6..0000000 Binary files a/project/target/classes/com/example/crawler/chart/ChartGenerator.class and /dev/null differ diff --git a/project/target/classes/com/example/crawler/command/BaseCrawlCommand.class b/project/target/classes/com/example/crawler/command/BaseCrawlCommand.class deleted file mode 100644 index 216241a..0000000 Binary files a/project/target/classes/com/example/crawler/command/BaseCrawlCommand.class and /dev/null differ diff --git a/project/target/classes/com/example/crawler/command/BookCommand.class b/project/target/classes/com/example/crawler/command/BookCommand.class deleted file mode 100644 index 3d0baf1..0000000 Binary files a/project/target/classes/com/example/crawler/command/BookCommand.class and /dev/null differ diff --git a/project/target/classes/com/example/crawler/command/Command.class b/project/target/classes/com/example/crawler/command/Command.class deleted file mode 100644 index 7bc1ab3..0000000 Binary files a/project/target/classes/com/example/crawler/command/Command.class and /dev/null differ diff --git a/project/target/classes/com/example/crawler/command/CrawlAllCommand.class b/project/target/classes/com/example/crawler/command/CrawlAllCommand.class deleted file mode 100644 index eeb9144..0000000 Binary files a/project/target/classes/com/example/crawler/command/CrawlAllCommand.class and /dev/null differ diff --git a/project/target/classes/com/example/crawler/command/CrawlAndAnalyzeAllCommand.class b/project/target/classes/com/example/crawler/command/CrawlAndAnalyzeAllCommand.class deleted file mode 100644 index a2db69d..0000000 Binary files a/project/target/classes/com/example/crawler/command/CrawlAndAnalyzeAllCommand.class and /dev/null differ diff --git a/project/target/classes/com/example/crawler/command/CrawlRankingCommand.class b/project/target/classes/com/example/crawler/command/CrawlRankingCommand.class deleted file mode 100644 index e65a8c4..0000000 Binary files a/project/target/classes/com/example/crawler/command/CrawlRankingCommand.class and /dev/null differ diff --git a/project/target/classes/com/example/crawler/command/ExitCommand.class b/project/target/classes/com/example/crawler/command/ExitCommand.class deleted file mode 100644 index 903b3a7..0000000 Binary files a/project/target/classes/com/example/crawler/command/ExitCommand.class and /dev/null differ diff --git a/project/target/classes/com/example/crawler/command/GenerateAllAnalysisCommand.class b/project/target/classes/com/example/crawler/command/GenerateAllAnalysisCommand.class deleted file mode 100644 index 60ef93b..0000000 Binary files a/project/target/classes/com/example/crawler/command/GenerateAllAnalysisCommand.class and /dev/null differ diff --git a/project/target/classes/com/example/crawler/command/NewsCommand.class b/project/target/classes/com/example/crawler/command/NewsCommand.class deleted file mode 100644 index b7c5106..0000000 Binary files a/project/target/classes/com/example/crawler/command/NewsCommand.class and /dev/null differ diff --git a/project/target/classes/com/example/crawler/command/SaveCommand.class b/project/target/classes/com/example/crawler/command/SaveCommand.class deleted file mode 100644 index 291183a..0000000 Binary files a/project/target/classes/com/example/crawler/command/SaveCommand.class and /dev/null differ diff --git a/project/target/classes/com/example/crawler/command/WeatherCommand.class b/project/target/classes/com/example/crawler/command/WeatherCommand.class deleted file mode 100644 index be29d68..0000000 Binary files a/project/target/classes/com/example/crawler/command/WeatherCommand.class and /dev/null differ diff --git a/project/target/classes/com/example/crawler/constant/CrawlerConstants.class b/project/target/classes/com/example/crawler/constant/CrawlerConstants.class deleted file mode 100644 index d201e06..0000000 Binary files a/project/target/classes/com/example/crawler/constant/CrawlerConstants.class and /dev/null differ diff --git a/project/target/classes/com/example/crawler/controller/CrawlerController.class b/project/target/classes/com/example/crawler/controller/CrawlerController.class deleted file mode 100644 index 6fb5ba5..0000000 Binary files a/project/target/classes/com/example/crawler/controller/CrawlerController.class and /dev/null differ diff --git a/project/target/classes/com/example/crawler/exception/CrawlException.class b/project/target/classes/com/example/crawler/exception/CrawlException.class deleted file mode 100644 index e48a793..0000000 Binary files a/project/target/classes/com/example/crawler/exception/CrawlException.class and /dev/null differ diff --git a/project/target/classes/com/example/crawler/exception/DataSaveException.class b/project/target/classes/com/example/crawler/exception/DataSaveException.class deleted file mode 100644 index dc59472..0000000 Binary files a/project/target/classes/com/example/crawler/exception/DataSaveException.class and /dev/null differ diff --git a/project/target/classes/com/example/crawler/exception/NetworkException.class b/project/target/classes/com/example/crawler/exception/NetworkException.class deleted file mode 100644 index 2327b22..0000000 Binary files a/project/target/classes/com/example/crawler/exception/NetworkException.class and /dev/null differ diff --git a/project/target/classes/com/example/crawler/exception/ParseException.class b/project/target/classes/com/example/crawler/exception/ParseException.class deleted file mode 100644 index 180dbca..0000000 Binary files a/project/target/classes/com/example/crawler/exception/ParseException.class and /dev/null differ diff --git a/project/target/classes/com/example/crawler/model/Book.class b/project/target/classes/com/example/crawler/model/Book.class deleted file mode 100644 index de5aa2b..0000000 Binary files a/project/target/classes/com/example/crawler/model/Book.class and /dev/null differ diff --git a/project/target/classes/com/example/crawler/model/News.class b/project/target/classes/com/example/crawler/model/News.class deleted file mode 100644 index 2f396b6..0000000 Binary files a/project/target/classes/com/example/crawler/model/News.class and /dev/null differ diff --git a/project/target/classes/com/example/crawler/model/UniversityRank.class b/project/target/classes/com/example/crawler/model/UniversityRank.class deleted file mode 100644 index 39bd8fa..0000000 Binary files a/project/target/classes/com/example/crawler/model/UniversityRank.class and /dev/null differ diff --git a/project/target/classes/com/example/crawler/model/Weather.class b/project/target/classes/com/example/crawler/model/Weather.class deleted file mode 100644 index 223a2d8..0000000 Binary files a/project/target/classes/com/example/crawler/model/Weather.class and /dev/null differ diff --git a/project/target/classes/com/example/crawler/repository/DataRepository.class b/project/target/classes/com/example/crawler/repository/DataRepository.class deleted file mode 100644 index 9325a6b..0000000 Binary files a/project/target/classes/com/example/crawler/repository/DataRepository.class and /dev/null differ diff --git a/project/target/classes/com/example/crawler/service/BookAnalysisService.class b/project/target/classes/com/example/crawler/service/BookAnalysisService.class deleted file mode 100644 index 3aac772..0000000 Binary files a/project/target/classes/com/example/crawler/service/BookAnalysisService.class and /dev/null differ diff --git a/project/target/classes/com/example/crawler/service/NewsAnalysisService.class b/project/target/classes/com/example/crawler/service/NewsAnalysisService.class deleted file mode 100644 index 347efc9..0000000 Binary files a/project/target/classes/com/example/crawler/service/NewsAnalysisService.class and /dev/null differ diff --git a/project/target/classes/com/example/crawler/service/RankingAnalysisService.class b/project/target/classes/com/example/crawler/service/RankingAnalysisService.class deleted file mode 100644 index fcbac49..0000000 Binary files a/project/target/classes/com/example/crawler/service/RankingAnalysisService.class and /dev/null differ diff --git a/project/target/classes/com/example/crawler/service/WeatherAnalysisService.class b/project/target/classes/com/example/crawler/service/WeatherAnalysisService.class deleted file mode 100644 index c6cd28c..0000000 Binary files a/project/target/classes/com/example/crawler/service/WeatherAnalysisService.class and /dev/null differ diff --git a/project/target/classes/com/example/crawler/strategy/BookCrawlStrategy.class b/project/target/classes/com/example/crawler/strategy/BookCrawlStrategy.class deleted file mode 100644 index c00eb38..0000000 Binary files a/project/target/classes/com/example/crawler/strategy/BookCrawlStrategy.class and /dev/null differ diff --git a/project/target/classes/com/example/crawler/strategy/CrawlStrategy.class b/project/target/classes/com/example/crawler/strategy/CrawlStrategy.class deleted file mode 100644 index dd7a055..0000000 Binary files a/project/target/classes/com/example/crawler/strategy/CrawlStrategy.class and /dev/null differ diff --git a/project/target/classes/com/example/crawler/strategy/NewsCrawlStrategy.class b/project/target/classes/com/example/crawler/strategy/NewsCrawlStrategy.class deleted file mode 100644 index d87e85e..0000000 Binary files a/project/target/classes/com/example/crawler/strategy/NewsCrawlStrategy.class and /dev/null differ diff --git a/project/target/classes/com/example/crawler/strategy/StrategyFactory.class b/project/target/classes/com/example/crawler/strategy/StrategyFactory.class deleted file mode 100644 index 7484a28..0000000 Binary files a/project/target/classes/com/example/crawler/strategy/StrategyFactory.class and /dev/null differ diff --git a/project/target/classes/com/example/crawler/strategy/UniversityRankCrawlStrategy.class b/project/target/classes/com/example/crawler/strategy/UniversityRankCrawlStrategy.class deleted file mode 100644 index 73618c1..0000000 Binary files a/project/target/classes/com/example/crawler/strategy/UniversityRankCrawlStrategy.class and /dev/null differ diff --git a/project/target/classes/com/example/crawler/strategy/WeatherCrawlStrategy.class b/project/target/classes/com/example/crawler/strategy/WeatherCrawlStrategy.class deleted file mode 100644 index aaad47c..0000000 Binary files a/project/target/classes/com/example/crawler/strategy/WeatherCrawlStrategy.class and /dev/null differ diff --git a/project/target/classes/com/example/crawler/util/DataCleaner.class b/project/target/classes/com/example/crawler/util/DataCleaner.class deleted file mode 100644 index 4fedbe3..0000000 Binary files a/project/target/classes/com/example/crawler/util/DataCleaner.class and /dev/null differ diff --git a/project/target/classes/com/example/crawler/util/HttpUtil.class b/project/target/classes/com/example/crawler/util/HttpUtil.class deleted file mode 100644 index a0e5905..0000000 Binary files a/project/target/classes/com/example/crawler/util/HttpUtil.class and /dev/null differ diff --git a/project/target/classes/com/example/crawler/util/JsonUtil.class b/project/target/classes/com/example/crawler/util/JsonUtil.class deleted file mode 100644 index b8af89d..0000000 Binary files a/project/target/classes/com/example/crawler/util/JsonUtil.class and /dev/null differ diff --git a/project/target/classes/com/example/crawler/view/CrawlerView.class b/project/target/classes/com/example/crawler/view/CrawlerView.class deleted file mode 100644 index 4d8e326..0000000 Binary files a/project/target/classes/com/example/crawler/view/CrawlerView.class and /dev/null differ diff --git a/project/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst b/project/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst deleted file mode 100644 index 784a2d4..0000000 --- a/project/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst +++ /dev/null @@ -1,27 +0,0 @@ -com\example\crawler\constant\CrawlerConstants.class -com\example\crawler\model\Weather.class -com\example\crawler\util\DataCleaner.class -com\example\crawler\strategy\NewsCrawlStrategy.class -com\example\crawler\util\HttpUtil.class -com\example\crawler\model\Book.class -com\example\crawler\service\BookAnalysisService.class -com\example\crawler\model\UniversityRank.class -com\example\crawler\strategy\WeatherCrawlStrategy.class -com\example\crawler\strategy\CrawlStrategy.class -com\example\crawler\exception\NetworkException.class -com\example\crawler\model\News.class -com\example\crawler\command\Command.class -com\example\crawler\strategy\StrategyFactory.class -com\example\crawler\command\ExitCommand.class -com\example\crawler\strategy\BookCrawlStrategy.class -com\example\crawler\exception\CrawlException.class -com\example\crawler\util\JsonUtil.class -com\example\crawler\strategy\UniversityRankCrawlStrategy.class -com\example\crawler\service\NewsAnalysisService.class -com\example\crawler\service\WeatherAnalysisService.class -com\example\crawler\exception\DataSaveException.class -com\example\crawler\repository\DataRepository.class -com\example\crawler\exception\ParseException.class -com\example\crawler\view\CrawlerView.class -com\example\crawler\chart\ChartGenerator.class -com\example\crawler\service\RankingAnalysisService.class diff --git a/project/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst b/project/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst deleted file mode 100644 index f0e9336..0000000 --- a/project/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst +++ /dev/null @@ -1,38 +0,0 @@ -C:\Users\Lenovo\Desktop\java\project\src\main\java\com\example\crawler\exception\NetworkException.java -C:\Users\Lenovo\Desktop\java\project\src\main\java\com\example\crawler\command\CrawlAllCommand.java -C:\Users\Lenovo\Desktop\java\project\src\main\java\com\example\crawler\service\BookAnalysisService.java -C:\Users\Lenovo\Desktop\java\project\src\main\java\com\example\crawler\util\DataCleaner.java -C:\Users\Lenovo\Desktop\java\project\src\main\java\com\example\crawler\command\BookCommand.java -C:\Users\Lenovo\Desktop\java\project\src\main\java\com\example\crawler\repository\DataRepository.java -C:\Users\Lenovo\Desktop\java\project\src\main\java\com\example\crawler\strategy\StrategyFactory.java -C:\Users\Lenovo\Desktop\java\project\src\main\java\com\example\crawler\util\HttpUtil.java -C:\Users\Lenovo\Desktop\java\project\src\main\java\com\example\crawler\service\RankingAnalysisService.java -C:\Users\Lenovo\Desktop\java\project\src\main\java\com\example\crawler\service\WeatherAnalysisService.java -C:\Users\Lenovo\Desktop\java\project\src\main\java\com\example\crawler\constant\CrawlerConstants.java -C:\Users\Lenovo\Desktop\java\project\src\main\java\com\example\crawler\strategy\BookCrawlStrategy.java -C:\Users\Lenovo\Desktop\java\project\src\main\java\com\example\crawler\model\UniversityRank.java -C:\Users\Lenovo\Desktop\java\project\src\main\java\com\example\crawler\command\NewsCommand.java -C:\Users\Lenovo\Desktop\java\project\src\main\java\com\example\crawler\command\Command.java -C:\Users\Lenovo\Desktop\java\project\src\main\java\com\example\crawler\model\Weather.java -C:\Users\Lenovo\Desktop\java\project\src\main\java\com\example\crawler\exception\ParseException.java -C:\Users\Lenovo\Desktop\java\project\src\main\java\com\example\crawler\command\GenerateAllAnalysisCommand.java -C:\Users\Lenovo\Desktop\java\project\src\main\java\com\example\crawler\strategy\UniversityRankCrawlStrategy.java -C:\Users\Lenovo\Desktop\java\project\src\main\java\com\example\crawler\util\JsonUtil.java -C:\Users\Lenovo\Desktop\java\project\src\main\java\com\example\crawler\command\CrawlAndAnalyzeAllCommand.java -C:\Users\Lenovo\Desktop\java\project\src\main\java\com\example\crawler\service\NewsAnalysisService.java -C:\Users\Lenovo\Desktop\java\project\src\main\java\com\example\crawler\command\CrawlRankingCommand.java -C:\Users\Lenovo\Desktop\java\project\src\main\java\com\example\crawler\exception\DataSaveException.java -C:\Users\Lenovo\Desktop\java\project\src\main\java\com\example\crawler\strategy\WeatherCrawlStrategy.java -C:\Users\Lenovo\Desktop\java\project\src\main\java\com\example\crawler\command\SaveCommand.java -C:\Users\Lenovo\Desktop\java\project\src\main\java\com\example\crawler\model\News.java -C:\Users\Lenovo\Desktop\java\project\src\main\java\com\example\crawler\command\BaseCrawlCommand.java -C:\Users\Lenovo\Desktop\java\project\src\main\java\com\example\crawler\controller\CrawlerController.java -C:\Users\Lenovo\Desktop\java\project\src\main\java\com\example\crawler\model\Book.java -C:\Users\Lenovo\Desktop\java\project\src\main\java\com\example\crawler\exception\CrawlException.java -C:\Users\Lenovo\Desktop\java\project\src\main\java\com\example\crawler\command\ExitCommand.java -C:\Users\Lenovo\Desktop\java\project\src\main\java\com\example\crawler\command\WeatherCommand.java -C:\Users\Lenovo\Desktop\java\project\src\main\java\com\example\crawler\chart\ChartGenerator.java -C:\Users\Lenovo\Desktop\java\project\src\main\java\com\example\crawler\strategy\CrawlStrategy.java -C:\Users\Lenovo\Desktop\java\project\src\main\java\com\example\crawler\Main.java -C:\Users\Lenovo\Desktop\java\project\src\main\java\com\example\crawler\strategy\NewsCrawlStrategy.java -C:\Users\Lenovo\Desktop\java\project\src\main\java\com\example\crawler\view\CrawlerView.java